Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 ////////////////////////////////////////////////////////////////////////////////
2 // Constants
4 const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT;
5 const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
6 const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
7 const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD;
8 const EVENT_DOCUMENT_LOAD_STOPPED = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_STOPPED;
9 const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
10 const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
11 const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
12 const EVENT_MENU_START = nsIAccessibleEvent.EVENT_MENU_START;
13 const EVENT_MENU_END = nsIAccessibleEvent.EVENT_MENU_END;
14 const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START;
15 const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END;
16 const EVENT_OBJECT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED;
17 const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
18 const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START;
19 const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION;
20 const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD;
21 const EVENT_SELECTION_REMOVE = nsIAccessibleEvent.EVENT_SELECTION_REMOVE;
22 const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
23 const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
24 const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
25 const EVENT_TEXT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED;
26 const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
27 const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
28 const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
29 const EVENT_TEXT_SELECTION_CHANGED = nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED;
30 const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
31 const EVENT_VIRTUALCURSOR_CHANGED = nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED;
33 const kNotFromUserInput = 0;
34 const kFromUserInput = 1;
36 ////////////////////////////////////////////////////////////////////////////////
37 // General
39 Components.utils.import("resource://gre/modules/Services.jsm");
41 /**
42 * Set up this variable to dump events into DOM.
43 */
44 var gA11yEventDumpID = "";
46 /**
47 * Set up this variable to dump event processing into console.
48 */
49 var gA11yEventDumpToConsole = false;
51 /**
52 * Set up this variable to dump event processing into error console.
53 */
54 var gA11yEventDumpToAppConsole = false;
56 /**
57 * Semicolon separated set of logging features.
58 */
59 var gA11yEventDumpFeature = "";
61 /**
62 * Executes the function when requested event is handled.
63 *
64 * @param aEventType [in] event type
65 * @param aTarget [in] event target
66 * @param aFunc [in] function to call when event is handled
67 * @param aContext [in, optional] object in which context the function is
68 * called
69 * @param aArg1 [in, optional] argument passed into the function
70 * @param aArg2 [in, optional] argument passed into the function
71 */
72 function waitForEvent(aEventType, aTargetOrFunc, aFunc, aContext, aArg1, aArg2)
73 {
74 var handler = {
75 handleEvent: function handleEvent(aEvent) {
77 var target = aTargetOrFunc;
78 if (typeof aTargetOrFunc == "function")
79 target = aTargetOrFunc.call();
81 if (target) {
82 if (target instanceof nsIAccessible &&
83 target != aEvent.accessible)
84 return;
86 if (target instanceof nsIDOMNode &&
87 target != aEvent.DOMNode)
88 return;
89 }
91 unregisterA11yEventListener(aEventType, this);
93 window.setTimeout(
94 function ()
95 {
96 aFunc.call(aContext, aArg1, aArg2);
97 },
98 0
99 );
100 }
101 };
103 registerA11yEventListener(aEventType, handler);
104 }
106 /**
107 * Generate mouse move over image map what creates image map accessible (async).
108 * See waitForImageMap() function.
109 */
110 function waveOverImageMap(aImageMapID)
111 {
112 var imageMapNode = getNode(aImageMapID);
113 synthesizeMouse(imageMapNode, 10, 10, { type: "mousemove" },
114 imageMapNode.ownerDocument.defaultView);
115 }
117 /**
118 * Call the given function when the tree of the given image map is built.
119 */
120 function waitForImageMap(aImageMapID, aTestFunc)
121 {
122 waveOverImageMap(aImageMapID);
124 var imageMapAcc = getAccessible(aImageMapID);
125 if (imageMapAcc.firstChild)
126 return aTestFunc();
128 waitForEvent(EVENT_REORDER, imageMapAcc, aTestFunc);
129 }
131 /**
132 * Register accessibility event listener.
133 *
134 * @param aEventType the accessible event type (see nsIAccessibleEvent for
135 * available constants).
136 * @param aEventHandler event listener object, when accessible event of the
137 * given type is handled then 'handleEvent' method of
138 * this object is invoked with nsIAccessibleEvent object
139 * as the first argument.
140 */
141 function registerA11yEventListener(aEventType, aEventHandler)
142 {
143 listenA11yEvents(true);
144 addA11yEventListener(aEventType, aEventHandler);
145 }
147 /**
148 * Unregister accessibility event listener. Must be called for every registered
149 * event listener (see registerA11yEventListener() function) when the listener
150 * is not needed.
151 */
152 function unregisterA11yEventListener(aEventType, aEventHandler)
153 {
154 removeA11yEventListener(aEventType, aEventHandler);
155 listenA11yEvents(false);
156 }
159 ////////////////////////////////////////////////////////////////////////////////
160 // Event queue
162 /**
163 * Return value of invoke method of invoker object. Indicates invoker was unable
164 * to prepare action.
165 */
166 const INVOKER_ACTION_FAILED = 1;
168 /**
169 * Return value of eventQueue.onFinish. Indicates eventQueue should not finish
170 * tests.
171 */
172 const DO_NOT_FINISH_TEST = 1;
174 /**
175 * Creates event queue for the given event type. The queue consists of invoker
176 * objects, each of them generates the event of the event type. When queue is
177 * started then every invoker object is asked to generate event after timeout.
178 * When event is caught then current invoker object is asked to check whether
179 * event was handled correctly.
180 *
181 * Invoker interface is:
182 *
183 * var invoker = {
184 * // Generates accessible event or event sequence. If returns
185 * // INVOKER_ACTION_FAILED constant then stop tests.
186 * invoke: function(){},
187 *
188 * // [optional] Invoker's check of handled event for correctness.
189 * check: function(aEvent){},
190 *
191 * // [optional] Invoker's check before the next invoker is proceeded.
192 * finalCheck: function(aEvent){},
193 *
194 * // [optional] Is called when event of any registered type is handled.
195 * debugCheck: function(aEvent){},
196 *
197 * // [ignored if 'eventSeq' is defined] DOM node event is generated for
198 * // (used in the case when invoker expects single event).
199 * DOMNode getter: function() {},
200 *
201 * // [optional] if true then event sequences are ignored (no failure if
202 * // sequences are empty). Use you need to invoke an action, do some check
203 * // after timeout and proceed a next invoker.
204 * noEventsOnAction getter: function() {},
205 *
206 * // Array of checker objects defining expected events on invoker's action.
207 * //
208 * // Checker object interface:
209 * //
210 * // var checker = {
211 * // * DOM or a11y event type. *
212 * // type getter: function() {},
213 * //
214 * // * DOM node or accessible. *
215 * // target getter: function() {},
216 * //
217 * // * DOM event phase (false - bubbling). *
218 * // phase getter: function() {},
219 * //
220 * // * Callback, called to match handled event. *
221 * // match : function(aEvent) {},
222 * //
223 * // * Callback, called when event is handled
224 * // check: function(aEvent) {},
225 * //
226 * // * Checker ID *
227 * // getID: function() {},
228 * //
229 * // * Event that don't have predefined order relative other events. *
230 * // async getter: function() {},
231 * //
232 * // * Event that is not expected. *
233 * // unexpected getter: function() {},
234 * //
235 * // * No other event of the same type is not allowed. *
236 * // unique getter: function() {}
237 * // };
238 * eventSeq getter() {},
239 *
240 * // Array of checker objects defining unexpected events on invoker's
241 * // action.
242 * unexpectedEventSeq getter() {},
243 *
244 * // The ID of invoker.
245 * getID: function(){} // returns invoker ID
246 * };
247 *
248 * // Used to add a possible scenario of expected/unexpected events on
249 * // invoker's action.
250 * defineScenario(aInvokerObj, aEventSeq, aUnexpectedEventSeq)
251 *
252 *
253 * @param aEventType [in, optional] the default event type (isn't used if
254 * invoker defines eventSeq property).
255 */
256 function eventQueue(aEventType)
257 {
258 // public
260 /**
261 * Add invoker object into queue.
262 */
263 this.push = function eventQueue_push(aEventInvoker)
264 {
265 this.mInvokers.push(aEventInvoker);
266 }
268 /**
269 * Start the queue processing.
270 */
271 this.invoke = function eventQueue_invoke()
272 {
273 listenA11yEvents(true);
275 // XXX: Intermittent test_events_caretmove.html fails withouth timeout,
276 // see bug 474952.
277 this.processNextInvokerInTimeout(true);
278 }
280 /**
281 * This function is called when all events in the queue were handled.
282 * Override it if you need to be notified of this.
283 */
284 this.onFinish = function eventQueue_finish()
285 {
286 }
288 // private
290 /**
291 * Process next invoker.
292 */
293 this.processNextInvoker = function eventQueue_processNextInvoker()
294 {
295 // Some scenario was matched, we wait on next invoker processing.
296 if (this.mNextInvokerStatus == kInvokerCanceled) {
297 this.setInvokerStatus(kInvokerNotScheduled,
298 "scenario was matched, wait for next invoker activation");
299 return;
300 }
302 this.setInvokerStatus(kInvokerNotScheduled, "the next invoker is processed now");
304 // Finish processing of the current invoker if any.
305 var testFailed = false;
307 var invoker = this.getInvoker();
308 if (invoker) {
309 if ("finalCheck" in invoker)
310 invoker.finalCheck();
312 if (this.mScenarios && this.mScenarios.length) {
313 var matchIdx = -1;
314 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
315 var eventSeq = this.mScenarios[scnIdx];
316 if (!this.areExpectedEventsLeft(eventSeq)) {
317 for (var idx = 0; idx < eventSeq.length; idx++) {
318 var checker = eventSeq[idx];
319 if (checker.unexpected && checker.wasCaught ||
320 !checker.unexpected && checker.wasCaught != 1) {
321 break;
322 }
323 }
325 // Ok, we have matched scenario. Report it was completed ok. In
326 // case of empty scenario guess it was matched but if later we
327 // find out that non empty scenario was matched then it will be
328 // a final match.
329 if (idx == eventSeq.length) {
330 if (matchIdx != -1 && eventSeq.length > 0 &&
331 this.mScenarios[matchIdx].length > 0) {
332 ok(false,
333 "We have a matched scenario at index " + matchIdx + " already.");
334 }
336 if (matchIdx == -1 || eventSeq.length > 0)
337 matchIdx = scnIdx;
339 // Report everythign is ok.
340 for (var idx = 0; idx < eventSeq.length; idx++) {
341 var checker = eventSeq[idx];
343 var typeStr = eventQueue.getEventTypeAsString(checker);
344 var msg = "Test with ID = '" + this.getEventID(checker) +
345 "' succeed. ";
347 if (checker.unexpected)
348 ok(true, msg + "There's no unexpected " + typeStr + " event.");
349 else
350 ok(true, msg + "Event " + typeStr + " was handled.");
351 }
352 }
353 }
354 }
356 // We don't have completely matched scenario. Report each failure/success
357 // for every scenario.
358 if (matchIdx == -1) {
359 testFailed = true;
360 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
361 var eventSeq = this.mScenarios[scnIdx];
362 for (var idx = 0; idx < eventSeq.length; idx++) {
363 var checker = eventSeq[idx];
365 var typeStr = eventQueue.getEventTypeAsString(checker);
366 var msg = "Scenario #" + scnIdx + " of test with ID = '" +
367 this.getEventID(checker) + "' failed. ";
369 if (checker.wasCaught > 1)
370 ok(false, msg + "Dupe " + typeStr + " event.");
372 if (checker.unexpected) {
373 if (checker.wasCaught)
374 ok(false, msg + "There's unexpected " + typeStr + " event.");
375 } else if (!checker.wasCaught) {
376 ok(false, msg + typeStr + " event was missed.");
377 }
378 }
379 }
380 }
381 }
382 }
384 this.clearEventHandler();
386 // Check if need to stop the test.
387 if (testFailed || this.mIndex == this.mInvokers.length - 1) {
388 listenA11yEvents(false);
390 var res = this.onFinish();
391 if (res != DO_NOT_FINISH_TEST)
392 SimpleTest.finish();
394 return;
395 }
397 // Start processing of next invoker.
398 invoker = this.getNextInvoker();
400 // Set up event listeners. Process a next invoker if no events were added.
401 if (!this.setEventHandler(invoker)) {
402 this.processNextInvoker();
403 return;
404 }
406 if (gLogger.isEnabled()) {
407 gLogger.logToConsole("Event queue: \n invoke: " + invoker.getID());
408 gLogger.logToDOM("EQ: invoke: " + invoker.getID(), true);
409 }
411 var infoText = "Invoke the '" + invoker.getID() + "' test { ";
412 var scnCount = this.mScenarios ? this.mScenarios.length : 0;
413 for (var scnIdx = 0; scnIdx < scnCount; scnIdx++) {
414 infoText += "scenario #" + scnIdx + ": ";
415 var eventSeq = this.mScenarios[scnIdx];
416 for (var idx = 0; idx < eventSeq.length; idx++) {
417 infoText += eventSeq[idx].unexpected ? "un" : "" +
418 "expected '" + eventQueue.getEventTypeAsString(eventSeq[idx]) +
419 "' event; ";
420 }
421 }
422 infoText += " }";
423 info(infoText);
425 if (invoker.invoke() == INVOKER_ACTION_FAILED) {
426 // Invoker failed to prepare action, fail and finish tests.
427 this.processNextInvoker();
428 return;
429 }
431 if (this.hasUnexpectedEventsScenario())
432 this.processNextInvokerInTimeout(true);
433 }
435 this.processNextInvokerInTimeout =
436 function eventQueue_processNextInvokerInTimeout(aUncondProcess)
437 {
438 this.setInvokerStatus(kInvokerPending, "Process next invoker in timeout");
440 // No need to wait extra timeout when a) we know we don't need to do that
441 // and b) there's no any single unexpected event.
442 if (!aUncondProcess && this.areAllEventsExpected()) {
443 // We need delay to avoid events coalesce from different invokers.
444 var queue = this;
445 SimpleTest.executeSoon(function() { queue.processNextInvoker(); });
446 return;
447 }
449 // Check in timeout invoker didn't fire registered events.
450 window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 300,
451 this);
452 }
454 /**
455 * Handle events for the current invoker.
456 */
457 this.handleEvent = function eventQueue_handleEvent(aEvent)
458 {
459 var invoker = this.getInvoker();
460 if (!invoker) // skip events before test was started
461 return;
463 if (!this.mScenarios) {
464 // Bad invoker object, error will be reported before processing of next
465 // invoker in the queue.
466 this.processNextInvoker();
467 return;
468 }
470 if ("debugCheck" in invoker)
471 invoker.debugCheck(aEvent);
473 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
474 var eventSeq = this.mScenarios[scnIdx];
475 for (var idx = 0; idx < eventSeq.length; idx++) {
476 var checker = eventSeq[idx];
478 // Search through handled expected events to report error if one of them
479 // is handled for a second time.
480 if (!checker.unexpected && (checker.wasCaught > 0) &&
481 eventQueue.isSameEvent(checker, aEvent)) {
482 checker.wasCaught++;
483 continue;
484 }
486 // Search through unexpected events, any match results in error report
487 // after this invoker processing (in case of matched scenario only).
488 if (checker.unexpected && eventQueue.compareEvents(checker, aEvent)) {
489 checker.wasCaught++;
490 continue;
491 }
493 // Report an error if we hanlded not expected event of unique type
494 // (i.e. event types are matched, targets differs).
495 if (!checker.unexpected && checker.unique &&
496 eventQueue.compareEventTypes(checker, aEvent)) {
497 var isExppected = false;
498 for (var jdx = 0; jdx < eventSeq.length; jdx++) {
499 isExpected = eventQueue.compareEvents(eventSeq[jdx], aEvent);
500 if (isExpected)
501 break;
502 }
504 if (!isExpected) {
505 ok(false,
506 "Unique type " +
507 eventQueue.getEventTypeAsString(checker) + " event was handled.");
508 }
509 }
510 }
511 }
513 var hasMatchedCheckers = false;
514 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
515 var eventSeq = this.mScenarios[scnIdx];
517 // Check if handled event matches expected sync event.
518 var nextChecker = this.getNextExpectedEvent(eventSeq);
519 if (nextChecker) {
520 if (eventQueue.compareEvents(nextChecker, aEvent)) {
521 this.processMatchedChecker(aEvent, nextChecker, scnIdx, eventSeq.idx);
522 hasMatchedCheckers = true;
523 continue;
524 }
525 }
527 // Check if handled event matches any expected async events.
528 for (idx = 0; idx < eventSeq.length; idx++) {
529 if (!eventSeq[idx].unexpected && eventSeq[idx].async) {
530 if (eventQueue.compareEvents(eventSeq[idx], aEvent)) {
531 this.processMatchedChecker(aEvent, eventSeq[idx], scnIdx, idx);
532 hasMatchedCheckers = true;
533 break;
534 }
535 }
536 }
537 }
539 if (hasMatchedCheckers) {
540 var invoker = this.getInvoker();
541 if ("check" in invoker)
542 invoker.check(aEvent);
543 }
545 // If we don't have more events to wait then schedule next invoker.
546 if (this.hasMatchedScenario()) {
547 if (this.mNextInvokerStatus == kInvokerNotScheduled) {
548 this.processNextInvokerInTimeout();
550 } else if (this.mNextInvokerStatus == kInvokerCanceled) {
551 this.setInvokerStatus(kInvokerPending,
552 "Full match. Void the cancelation of next invoker processing");
553 }
554 return;
555 }
557 // If we have scheduled a next invoker then cancel in case of match.
558 if ((this.mNextInvokerStatus == kInvokerPending) && hasMatchedCheckers) {
559 this.setInvokerStatus(kInvokerCanceled,
560 "Cancel the scheduled invoker in case of match");
561 }
562 }
564 // Helpers
565 this.processMatchedChecker =
566 function eventQueue_function(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx)
567 {
568 aMatchedChecker.wasCaught++;
570 if ("check" in aMatchedChecker)
571 aMatchedChecker.check(aEvent);
573 eventQueue.logEvent(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx,
574 this.areExpectedEventsLeft(),
575 this.mNextInvokerStatus);
576 }
578 this.getNextExpectedEvent =
579 function eventQueue_getNextExpectedEvent(aEventSeq)
580 {
581 if (!("idx" in aEventSeq))
582 aEventSeq.idx = 0;
584 while (aEventSeq.idx < aEventSeq.length &&
585 (aEventSeq[aEventSeq.idx].unexpected ||
586 aEventSeq[aEventSeq.idx].async ||
587 aEventSeq[aEventSeq.idx].wasCaught > 0)) {
588 aEventSeq.idx++;
589 }
591 return aEventSeq.idx != aEventSeq.length ? aEventSeq[aEventSeq.idx] : null;
592 }
594 this.areExpectedEventsLeft =
595 function eventQueue_areExpectedEventsLeft(aScenario)
596 {
597 function scenarioHasUnhandledExpectedEvent(aEventSeq)
598 {
599 // Check if we have unhandled async (can be anywhere in the sequance) or
600 // sync expcected events yet.
601 for (var idx = 0; idx < aEventSeq.length; idx++) {
602 if (!aEventSeq[idx].unexpected && !aEventSeq[idx].wasCaught)
603 return true;
604 }
606 return false;
607 }
609 if (aScenario)
610 return scenarioHasUnhandledExpectedEvent(aScenario);
612 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
613 var eventSeq = this.mScenarios[scnIdx];
614 if (scenarioHasUnhandledExpectedEvent(eventSeq))
615 return true;
616 }
617 return false;
618 }
620 this.areAllEventsExpected =
621 function eventQueue_areAllEventsExpected()
622 {
623 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
624 var eventSeq = this.mScenarios[scnIdx];
625 for (var idx = 0; idx < eventSeq.length; idx++) {
626 if (eventSeq[idx].unexpected)
627 return false;
628 }
629 }
631 return true;
632 }
634 this.isUnexpectedEventScenario =
635 function eventQueue_isUnexpectedEventsScenario(aScenario)
636 {
637 for (var idx = 0; idx < aScenario.length; idx++) {
638 if (!aScenario[idx].unexpected)
639 break;
640 }
642 return idx == aScenario.length;
643 }
645 this.hasUnexpectedEventsScenario =
646 function eventQueue_hasUnexpectedEventsScenario()
647 {
648 if (this.getInvoker().noEventsOnAction)
649 return true;
651 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
652 if (this.isUnexpectedEventScenario(this.mScenarios[scnIdx]))
653 return true;
654 }
656 return false;
657 }
659 this.hasMatchedScenario =
660 function eventQueue_hasMatchedScenario()
661 {
662 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
663 var scn = this.mScenarios[scnIdx];
664 if (!this.isUnexpectedEventScenario(scn) && !this.areExpectedEventsLeft(scn))
665 return true;
666 }
667 return false;
668 }
670 this.getInvoker = function eventQueue_getInvoker()
671 {
672 return this.mInvokers[this.mIndex];
673 }
675 this.getNextInvoker = function eventQueue_getNextInvoker()
676 {
677 return this.mInvokers[++this.mIndex];
678 }
680 this.setEventHandler = function eventQueue_setEventHandler(aInvoker)
681 {
682 if (!("scenarios" in aInvoker) || aInvoker.scenarios.length == 0) {
683 var eventSeq = aInvoker.eventSeq;
684 var unexpectedEventSeq = aInvoker.unexpectedEventSeq;
685 if (!eventSeq && !unexpectedEventSeq && this.mDefEventType)
686 eventSeq = [ new invokerChecker(this.mDefEventType, aInvoker.DOMNode) ];
688 if (eventSeq || unexpectedEventSeq)
689 defineScenario(aInvoker, eventSeq, unexpectedEventSeq);
690 }
692 if (aInvoker.noEventsOnAction)
693 return true;
695 this.mScenarios = aInvoker.scenarios;
696 if (!this.mScenarios || !this.mScenarios.length) {
697 ok(false, "Broken invoker '" + aInvoker.getID() + "'");
698 return false;
699 }
701 // Register event listeners.
702 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
703 var eventSeq = this.mScenarios[scnIdx];
705 if (gLogger.isEnabled()) {
706 var msg = "scenario #" + scnIdx +
707 ", registered events number: " + eventSeq.length;
708 gLogger.logToConsole(msg);
709 gLogger.logToDOM(msg, true);
710 }
712 // Do not warn about empty event sequances when more than one scenario
713 // was registered.
714 if (this.mScenarios.length == 1 && eventSeq.length == 0) {
715 ok(false,
716 "Broken scenario #" + scnIdx + " of invoker '" + aInvoker.getID() +
717 "'. No registered events");
718 return false;
719 }
721 for (var idx = 0; idx < eventSeq.length; idx++)
722 eventSeq[idx].wasCaught = 0;
724 for (var idx = 0; idx < eventSeq.length; idx++) {
725 if (gLogger.isEnabled()) {
726 var msg = "registered";
727 if (eventSeq[idx].unexpected)
728 msg += " unexpected";
729 if (eventSeq[idx].async)
730 msg += " async";
732 msg += ": event type: " +
733 eventQueue.getEventTypeAsString(eventSeq[idx]) +
734 ", target: " + eventQueue.getEventTargetDescr(eventSeq[idx], true);
736 gLogger.logToConsole(msg);
737 gLogger.logToDOM(msg, true);
738 }
740 var eventType = eventSeq[idx].type;
741 if (typeof eventType == "string") {
742 // DOM event
743 var target = eventSeq[idx].target;
744 if (!target) {
745 ok(false, "no target for DOM event!");
746 return false;
747 }
748 var phase = eventQueue.getEventPhase(eventSeq[idx]);
749 target.ownerDocument.addEventListener(eventType, this, phase);
751 } else {
752 // A11y event
753 addA11yEventListener(eventType, this);
754 }
755 }
756 }
758 return true;
759 }
761 this.clearEventHandler = function eventQueue_clearEventHandler()
762 {
763 if (!this.mScenarios)
764 return;
766 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
767 var eventSeq = this.mScenarios[scnIdx];
768 for (var idx = 0; idx < eventSeq.length; idx++) {
769 var eventType = eventSeq[idx].type;
770 if (typeof eventType == "string") {
771 // DOM event
772 var target = eventSeq[idx].target;
773 var phase = eventQueue.getEventPhase(eventSeq[idx]);
774 target.ownerDocument.removeEventListener(eventType, this, phase);
776 } else {
777 // A11y event
778 removeA11yEventListener(eventType, this);
779 }
780 }
781 }
782 this.mScenarios = null;
783 }
785 this.getEventID = function eventQueue_getEventID(aChecker)
786 {
787 if ("getID" in aChecker)
788 return aChecker.getID();
790 var invoker = this.getInvoker();
791 return invoker.getID();
792 }
794 this.setInvokerStatus = function eventQueue_setInvokerStatus(aStatus, aLogMsg)
795 {
796 this.mNextInvokerStatus = aStatus;
798 // Uncomment it to debug invoker processing logic.
799 //gLogger.log(eventQueue.invokerStatusToMsg(aStatus, aLogMsg));
800 }
802 this.mDefEventType = aEventType;
804 this.mInvokers = new Array();
805 this.mIndex = -1;
806 this.mScenarios = null;
808 this.mNextInvokerStatus = kInvokerNotScheduled;
809 }
811 ////////////////////////////////////////////////////////////////////////////////
812 // eventQueue static members and constants
814 const kInvokerNotScheduled = 0;
815 const kInvokerPending = 1;
816 const kInvokerCanceled = 2;
818 eventQueue.getEventTypeAsString =
819 function eventQueue_getEventTypeAsString(aEventOrChecker)
820 {
821 if (aEventOrChecker instanceof nsIDOMEvent)
822 return aEventOrChecker.type;
824 if (aEventOrChecker instanceof nsIAccessibleEvent)
825 return eventTypeToString(aEventOrChecker.eventType);
827 return (typeof aEventOrChecker.type == "string") ?
828 aEventOrChecker.type : eventTypeToString(aEventOrChecker.type);
829 }
831 eventQueue.getEventTargetDescr =
832 function eventQueue_getEventTargetDescr(aEventOrChecker, aDontForceTarget)
833 {
834 if (aEventOrChecker instanceof nsIDOMEvent)
835 return prettyName(aEventOrChecker.originalTarget);
837 if (aEventOrChecker instanceof nsIDOMEvent)
838 return prettyName(aEventOrChecker.accessible);
840 var descr = aEventOrChecker.targetDescr;
841 if (descr)
842 return descr;
844 if (aDontForceTarget)
845 return "no target description";
847 var target = ("target" in aEventOrChecker) ? aEventOrChecker.target : null;
848 return prettyName(target);
849 }
851 eventQueue.getEventPhase = function eventQueue_getEventPhase(aChecker)
852 {
853 return ("phase" in aChecker) ? aChecker.phase : true;
854 }
856 eventQueue.compareEventTypes =
857 function eventQueue_compareEventTypes(aChecker, aEvent)
858 {
859 var eventType = (aEvent instanceof nsIDOMEvent) ?
860 aEvent.type : aEvent.eventType;
861 return aChecker.type == eventType;
862 }
864 eventQueue.compareEvents = function eventQueue_compareEvents(aChecker, aEvent)
865 {
866 if (!eventQueue.compareEventTypes(aChecker, aEvent))
867 return false;
869 // If checker provides "match" function then allow the checker to decide
870 // whether event is matched.
871 if ("match" in aChecker)
872 return aChecker.match(aEvent);
874 var target1 = aChecker.target;
875 if (target1 instanceof nsIAccessible) {
876 var target2 = (aEvent instanceof nsIDOMEvent) ?
877 getAccessible(aEvent.target) : aEvent.accessible;
879 return target1 == target2;
880 }
882 // If original target isn't suitable then extend interface to support target
883 // (original target is used in test_elm_media.html).
884 var target2 = (aEvent instanceof nsIDOMEvent) ?
885 aEvent.originalTarget : aEvent.DOMNode;
886 return target1 == target2;
887 }
889 eventQueue.isSameEvent = function eventQueue_isSameEvent(aChecker, aEvent)
890 {
891 // We don't have stored info about handled event other than its type and
892 // target, thus we should filter text change and state change events since
893 // they may occur on the same element because of complex changes.
894 return this.compareEvents(aChecker, aEvent) &&
895 !(aEvent instanceof nsIAccessibleTextChangeEvent) &&
896 !(aEvent instanceof nsIAccessibleStateChangeEvent);
897 }
899 eventQueue.invokerStatusToMsg =
900 function eventQueue_invokerStatusToMsg(aInvokerStatus, aMsg)
901 {
902 var msg = "invoker status: ";
903 switch (aInvokerStatus) {
904 case kInvokerNotScheduled:
905 msg += "not scheduled";
906 break;
907 case kInvokerPending:
908 msg += "pending";
909 break;
910 case kInvokerCanceled:
911 msg += "canceled";
912 break;
913 }
915 if (aMsg)
916 msg += " (" + aMsg + ")";
918 return msg;
919 }
921 eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
922 aScenarioIdx, aEventIdx,
923 aAreExpectedEventsLeft,
924 aInvokerStatus)
925 {
926 // Dump DOM event information. Skip a11y event since it is dumped by
927 // gA11yEventObserver.
928 if (aOrigEvent instanceof nsIDOMEvent) {
929 var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent);
930 info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent);
931 gLogger.logToDOM(info);
932 }
934 var infoMsg = "unhandled expected events: " + aAreExpectedEventsLeft +
935 ", " + eventQueue.invokerStatusToMsg(aInvokerStatus);
937 var currType = eventQueue.getEventTypeAsString(aMatchedChecker);
938 var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker);
939 var consoleMsg = "*****\nScenario " + aScenarioIdx +
940 ", event " + aEventIdx + " matched: " + currType + "\n" + infoMsg + "\n*****";
941 gLogger.logToConsole(consoleMsg);
943 var emphText = "matched ";
944 var msg = "EQ event, type: " + currType + ", target: " + currTargetDescr +
945 ", " + infoMsg;
946 gLogger.logToDOM(msg, true, emphText);
947 }
950 ////////////////////////////////////////////////////////////////////////////////
951 // Action sequence
953 /**
954 * Deal with action sequence. Used when you need to execute couple of actions
955 * each after other one.
956 */
957 function sequence()
958 {
959 /**
960 * Append new sequence item.
961 *
962 * @param aProcessor [in] object implementing interface
963 * {
964 * // execute item action
965 * process: function() {},
966 * // callback, is called when item was processed
967 * onProcessed: function() {}
968 * };
969 * @param aEventType [in] event type of expected event on item action
970 * @param aTarget [in] event target of expected event on item action
971 * @param aItemID [in] identifier of item
972 */
973 this.append = function sequence_append(aProcessor, aEventType, aTarget,
974 aItemID)
975 {
976 var item = new sequenceItem(aProcessor, aEventType, aTarget, aItemID);
977 this.items.push(item);
978 }
980 /**
981 * Process next sequence item.
982 */
983 this.processNext = function sequence_processNext()
984 {
985 this.idx++;
986 if (this.idx >= this.items.length) {
987 ok(false, "End of sequence: nothing to process!");
988 SimpleTest.finish();
989 return;
990 }
992 this.items[this.idx].startProcess();
993 }
995 this.items = new Array();
996 this.idx = -1;
997 }
1000 ////////////////////////////////////////////////////////////////////////////////
1001 // Event queue invokers
1003 /**
1004 * Defines a scenario of expected/unexpected events. Each invoker can have
1005 * one or more scenarios of events. Only one scenario must be completed.
1006 */
1007 function defineScenario(aInvoker, aEventSeq, aUnexpectedEventSeq)
1008 {
1009 if (!("scenarios" in aInvoker))
1010 aInvoker.scenarios = new Array();
1012 // Create unified event sequence concatenating expected and unexpected
1013 // events.
1014 if (!aEventSeq)
1015 aEventSeq = [];
1017 for (var idx = 0; idx < aEventSeq.length; idx++) {
1018 aEventSeq[idx].unexpected |= false;
1019 aEventSeq[idx].async |= false;
1020 }
1022 if (aUnexpectedEventSeq) {
1023 for (var idx = 0; idx < aUnexpectedEventSeq.length; idx++) {
1024 aUnexpectedEventSeq[idx].unexpected = true;
1025 aUnexpectedEventSeq[idx].async = false;
1026 }
1028 aEventSeq = aEventSeq.concat(aUnexpectedEventSeq);
1029 }
1031 aInvoker.scenarios.push(aEventSeq);
1032 }
1035 /**
1036 * Invokers defined below take a checker object (or array of checker objects).
1037 * An invoker listens for default event type registered in event queue object
1038 * until its checker is provided.
1039 *
1040 * Note, checker object or array of checker objects is optional.
1041 */
1043 /**
1044 * Click invoker.
1045 */
1046 function synthClick(aNodeOrID, aCheckerOrEventSeq, aArgs)
1047 {
1048 this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
1050 this.invoke = function synthClick_invoke()
1051 {
1052 var targetNode = this.DOMNode;
1053 if (targetNode instanceof nsIDOMDocument) {
1054 targetNode =
1055 this.DOMNode.body ? this.DOMNode.body : this.DOMNode.documentElement;
1056 }
1058 // Scroll the node into view, otherwise synth click may fail.
1059 if (targetNode instanceof nsIDOMHTMLElement) {
1060 targetNode.scrollIntoView(true);
1061 } else if (targetNode instanceof nsIDOMXULElement) {
1062 var targetAcc = getAccessible(targetNode);
1063 targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
1064 }
1066 var x = 1, y = 1;
1067 if (aArgs && ("where" in aArgs) && aArgs.where == "right") {
1068 if (targetNode instanceof nsIDOMHTMLElement)
1069 x = targetNode.offsetWidth - 1;
1070 else if (targetNode instanceof nsIDOMXULElement)
1071 x = targetNode.boxObject.width - 1;
1072 }
1073 synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {});
1074 }
1076 this.finalCheck = function synthClick_finalCheck()
1077 {
1078 // Scroll top window back.
1079 window.top.scrollTo(0, 0);
1080 }
1082 this.getID = function synthClick_getID()
1083 {
1084 return prettyName(aNodeOrID) + " click";
1085 }
1086 }
1088 /**
1089 * Mouse move invoker.
1090 */
1091 function synthMouseMove(aID, aCheckerOrEventSeq)
1092 {
1093 this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
1095 this.invoke = function synthMouseMove_invoke()
1096 {
1097 synthesizeMouse(this.DOMNode, 1, 1, { type: "mousemove" });
1098 synthesizeMouse(this.DOMNode, 2, 2, { type: "mousemove" });
1099 }
1101 this.getID = function synthMouseMove_getID()
1102 {
1103 return prettyName(aID) + " mouse move";
1104 }
1105 }
1107 /**
1108 * General key press invoker.
1109 */
1110 function synthKey(aNodeOrID, aKey, aArgs, aCheckerOrEventSeq)
1111 {
1112 this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
1114 this.invoke = function synthKey_invoke()
1115 {
1116 synthesizeKey(this.mKey, this.mArgs, this.mWindow);
1117 }
1119 this.getID = function synthKey_getID()
1120 {
1121 var key = this.mKey;
1122 switch (this.mKey) {
1123 case "VK_TAB":
1124 key = "tab";
1125 break;
1126 case "VK_DOWN":
1127 key = "down";
1128 break;
1129 case "VK_UP":
1130 key = "up";
1131 break;
1132 case "VK_LEFT":
1133 key = "left";
1134 break;
1135 case "VK_RIGHT":
1136 key = "right";
1137 break;
1138 case "VK_HOME":
1139 key = "home";
1140 break;
1141 case "VK_END":
1142 key = "end";
1143 break;
1144 case "VK_ESCAPE":
1145 key = "escape";
1146 break;
1147 case "VK_RETURN":
1148 key = "enter";
1149 break;
1150 }
1151 if (aArgs) {
1152 if (aArgs.shiftKey)
1153 key += " shift";
1154 if (aArgs.ctrlKey)
1155 key += " ctrl";
1156 if (aArgs.altKey)
1157 key += " alt";
1158 }
1159 return prettyName(aNodeOrID) + " '" + key + " ' key";
1160 }
1162 this.mKey = aKey;
1163 this.mArgs = aArgs ? aArgs : {};
1164 this.mWindow = aArgs ? aArgs.window : null;
1165 }
1167 /**
1168 * Tab key invoker.
1169 */
1170 function synthTab(aNodeOrID, aCheckerOrEventSeq, aWindow)
1171 {
1172 this.__proto__ = new synthKey(aNodeOrID, "VK_TAB",
1173 { shiftKey: false, window: aWindow },
1174 aCheckerOrEventSeq);
1175 }
1177 /**
1178 * Shift tab key invoker.
1179 */
1180 function synthShiftTab(aNodeOrID, aCheckerOrEventSeq)
1181 {
1182 this.__proto__ = new synthKey(aNodeOrID, "VK_TAB", { shiftKey: true },
1183 aCheckerOrEventSeq);
1184 }
1186 /**
1187 * Escape key invoker.
1188 */
1189 function synthEscapeKey(aNodeOrID, aCheckerOrEventSeq)
1190 {
1191 this.__proto__ = new synthKey(aNodeOrID, "VK_ESCAPE", null,
1192 aCheckerOrEventSeq);
1193 }
1195 /**
1196 * Down arrow key invoker.
1197 */
1198 function synthDownKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
1199 {
1200 this.__proto__ = new synthKey(aNodeOrID, "VK_DOWN", aArgs,
1201 aCheckerOrEventSeq);
1202 }
1204 /**
1205 * Up arrow key invoker.
1206 */
1207 function synthUpKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
1208 {
1209 this.__proto__ = new synthKey(aNodeOrID, "VK_UP", aArgs,
1210 aCheckerOrEventSeq);
1211 }
1213 /**
1214 * Left arrow key invoker.
1215 */
1216 function synthLeftKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
1217 {
1218 this.__proto__ = new synthKey(aNodeOrID, "VK_LEFT", aArgs, aCheckerOrEventSeq);
1219 }
1221 /**
1222 * Right arrow key invoker.
1223 */
1224 function synthRightKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
1225 {
1226 this.__proto__ = new synthKey(aNodeOrID, "VK_RIGHT", aArgs, aCheckerOrEventSeq);
1227 }
1229 /**
1230 * Home key invoker.
1231 */
1232 function synthHomeKey(aNodeOrID, aCheckerOrEventSeq)
1233 {
1234 this.__proto__ = new synthKey(aNodeOrID, "VK_HOME", null, aCheckerOrEventSeq);
1235 }
1237 /**
1238 * End key invoker.
1239 */
1240 function synthEndKey(aNodeOrID, aCheckerOrEventSeq)
1241 {
1242 this.__proto__ = new synthKey(aNodeOrID, "VK_END", null, aCheckerOrEventSeq);
1243 }
1245 /**
1246 * Enter key invoker
1247 */
1248 function synthEnterKey(aID, aCheckerOrEventSeq)
1249 {
1250 this.__proto__ = new synthKey(aID, "VK_RETURN", null, aCheckerOrEventSeq);
1251 }
1253 /**
1254 * Synth alt + down arrow to open combobox.
1255 */
1256 function synthOpenComboboxKey(aID, aCheckerOrEventSeq)
1257 {
1258 this.__proto__ = new synthDownKey(aID, aCheckerOrEventSeq, { altKey: true });
1260 this.getID = function synthOpenComboboxKey_getID()
1261 {
1262 return "open combobox (atl + down arrow) " + prettyName(aID);
1263 }
1264 }
1266 /**
1267 * Focus invoker.
1268 */
1269 function synthFocus(aNodeOrID, aCheckerOrEventSeq)
1270 {
1271 var checkerOfEventSeq =
1272 aCheckerOrEventSeq ? aCheckerOrEventSeq : new focusChecker(aNodeOrID);
1273 this.__proto__ = new synthAction(aNodeOrID, checkerOfEventSeq);
1275 this.invoke = function synthFocus_invoke()
1276 {
1277 if (this.DOMNode instanceof Components.interfaces.nsIDOMNSEditableElement &&
1278 this.DOMNode.editor ||
1279 this.DOMNode instanceof Components.interfaces.nsIDOMXULTextBoxElement) {
1280 this.DOMNode.selectionStart = this.DOMNode.selectionEnd = this.DOMNode.value.length;
1281 }
1282 this.DOMNode.focus();
1283 }
1285 this.getID = function synthFocus_getID()
1286 {
1287 return prettyName(aNodeOrID) + " focus";
1288 }
1289 }
1291 /**
1292 * Focus invoker. Focus the HTML body of content document of iframe.
1293 */
1294 function synthFocusOnFrame(aNodeOrID, aCheckerOrEventSeq)
1295 {
1296 var frameDoc = getNode(aNodeOrID).contentDocument;
1297 var checkerOrEventSeq =
1298 aCheckerOrEventSeq ? aCheckerOrEventSeq : new focusChecker(frameDoc);
1299 this.__proto__ = new synthAction(frameDoc, checkerOrEventSeq);
1301 this.invoke = function synthFocus_invoke()
1302 {
1303 this.DOMNode.body.focus();
1304 }
1306 this.getID = function synthFocus_getID()
1307 {
1308 return prettyName(aNodeOrID) + " frame document focus";
1309 }
1310 }
1312 /**
1313 * Change the current item when the widget doesn't have a focus.
1314 */
1315 function changeCurrentItem(aID, aItemID)
1316 {
1317 this.eventSeq = [ new nofocusChecker() ];
1319 this.invoke = function changeCurrentItem_invoke()
1320 {
1321 var controlNode = getNode(aID);
1322 var itemNode = getNode(aItemID);
1324 // HTML
1325 if (controlNode.localName == "input") {
1326 if (controlNode.checked)
1327 this.reportError();
1329 controlNode.checked = true;
1330 return;
1331 }
1333 if (controlNode.localName == "select") {
1334 if (controlNode.selectedIndex == itemNode.index)
1335 this.reportError();
1337 controlNode.selectedIndex = itemNode.index;
1338 return;
1339 }
1341 // XUL
1342 if (controlNode.localName == "tree") {
1343 if (controlNode.currentIndex == aItemID)
1344 this.reportError();
1346 controlNode.currentIndex = aItemID;
1347 return;
1348 }
1350 if (controlNode.localName == "menulist") {
1351 if (controlNode.selectedItem == itemNode)
1352 this.reportError();
1354 controlNode.selectedItem = itemNode;
1355 return;
1356 }
1358 if (controlNode.currentItem == itemNode)
1359 ok(false, "Error in test: proposed current item is already current" + prettyName(aID));
1361 controlNode.currentItem = itemNode;
1362 }
1364 this.getID = function changeCurrentItem_getID()
1365 {
1366 return "current item change for " + prettyName(aID);
1367 }
1369 this.reportError = function changeCurrentItem_reportError()
1370 {
1371 ok(false,
1372 "Error in test: proposed current item '" + aItemID + "' is already current");
1373 }
1374 }
1376 /**
1377 * Toggle top menu invoker.
1378 */
1379 function toggleTopMenu(aID, aCheckerOrEventSeq)
1380 {
1381 this.__proto__ = new synthKey(aID, "VK_ALT", null,
1382 aCheckerOrEventSeq);
1384 this.getID = function toggleTopMenu_getID()
1385 {
1386 return "toggle top menu on " + prettyName(aID);
1387 }
1388 }
1390 /**
1391 * Context menu invoker.
1392 */
1393 function synthContextMenu(aID, aCheckerOrEventSeq)
1394 {
1395 this.__proto__ = new synthClick(aID, aCheckerOrEventSeq,
1396 { button: 0, type: "contextmenu" });
1398 this.getID = function synthContextMenu_getID()
1399 {
1400 return "context menu on " + prettyName(aID);
1401 }
1402 }
1404 /**
1405 * Open combobox, autocomplete and etc popup, check expandable states.
1406 */
1407 function openCombobox(aComboboxID)
1408 {
1409 this.eventSeq = [
1410 new stateChangeChecker(STATE_EXPANDED, false, true, aComboboxID)
1411 ];
1413 this.invoke = function openCombobox_invoke()
1414 {
1415 getNode(aComboboxID).focus();
1416 synthesizeKey("VK_DOWN", { altKey: true });
1417 }
1419 this.getID = function openCombobox_getID()
1420 {
1421 return "open combobox " + prettyName(aComboboxID);
1422 }
1423 }
1425 /**
1426 * Close combobox, autocomplete and etc popup, check expandable states.
1427 */
1428 function closeCombobox(aComboboxID)
1429 {
1430 this.eventSeq = [
1431 new stateChangeChecker(STATE_EXPANDED, false, false, aComboboxID)
1432 ];
1434 this.invoke = function closeCombobox_invoke()
1435 {
1436 synthesizeKey("VK_ESCAPE", { });
1437 }
1439 this.getID = function closeCombobox_getID()
1440 {
1441 return "close combobox " + prettyName(aComboboxID);
1442 }
1443 }
1446 /**
1447 * Select all invoker.
1448 */
1449 function synthSelectAll(aNodeOrID, aCheckerOrEventSeq)
1450 {
1451 this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
1453 this.invoke = function synthSelectAll_invoke()
1454 {
1455 if (this.DOMNode instanceof Components.interfaces.nsIDOMHTMLInputElement ||
1456 this.DOMNode instanceof Components.interfaces.nsIDOMXULTextBoxElement) {
1457 this.DOMNode.select();
1459 } else {
1460 window.getSelection().selectAllChildren(this.DOMNode);
1461 }
1462 }
1464 this.getID = function synthSelectAll_getID()
1465 {
1466 return aNodeOrID + " selectall";
1467 }
1468 }
1470 /**
1471 * Move the caret to the end of line.
1472 */
1473 function moveToLineEnd(aID, aCaretOffset)
1474 {
1475 if (MAC) {
1476 this.__proto__ = new synthKey(aID, "VK_RIGHT", { metaKey: true },
1477 new caretMoveChecker(aCaretOffset, aID));
1478 } else {
1479 this.__proto__ = new synthEndKey(aID,
1480 new caretMoveChecker(aCaretOffset, aID));
1481 }
1483 this.getID = function moveToLineEnd_getID()
1484 {
1485 return "move to line end in " + prettyName(aID);
1486 }
1487 }
1489 /**
1490 * Move the caret to the end of previous line if any.
1491 */
1492 function moveToPrevLineEnd(aID, aCaretOffset)
1493 {
1494 this.__proto__ = new synthAction(aID, new caretMoveChecker(aCaretOffset, aID));
1496 this.invoke = function moveToPrevLineEnd_invoke()
1497 {
1498 synthesizeKey("VK_UP", { });
1500 if (MAC)
1501 synthesizeKey("VK_RIGHT", { metaKey: true });
1502 else
1503 synthesizeKey("VK_END", { });
1504 }
1506 this.getID = function moveToPrevLineEnd_getID()
1507 {
1508 return "move to previous line end in " + prettyName(aID);
1509 }
1510 }
1512 /**
1513 * Move the caret to begining of the line.
1514 */
1515 function moveToLineStart(aID, aCaretOffset)
1516 {
1517 if (MAC) {
1518 this.__proto__ = new synthKey(aID, "VK_LEFT", { metaKey: true },
1519 new caretMoveChecker(aCaretOffset, aID));
1520 } else {
1521 this.__proto__ = new synthHomeKey(aID,
1522 new caretMoveChecker(aCaretOffset, aID));
1523 }
1525 this.getID = function moveToLineEnd_getID()
1526 {
1527 return "move to line start in " + prettyName(aID);
1528 }
1529 }
1531 /**
1532 * Move the caret to begining of the text.
1533 */
1534 function moveToTextStart(aID)
1535 {
1536 if (MAC) {
1537 this.__proto__ = new synthKey(aID, "VK_UP", { metaKey: true },
1538 new caretMoveChecker(0, aID));
1539 } else {
1540 this.__proto__ = new synthKey(aID, "VK_HOME", { ctrlKey: true },
1541 new caretMoveChecker(0, aID));
1542 }
1544 this.getID = function moveToTextStart_getID()
1545 {
1546 return "move to text start in " + prettyName(aID);
1547 }
1548 }
1550 /**
1551 * Move the caret in text accessible.
1552 */
1553 function moveCaretToDOMPoint(aID, aDOMPointNodeID, aDOMPointOffset,
1554 aExpectedOffset, aFocusTargetID,
1555 aCheckFunc)
1556 {
1557 this.target = getAccessible(aID, [nsIAccessibleText]);
1558 this.DOMPointNode = getNode(aDOMPointNodeID);
1559 this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
1560 this.focusNode = this.focus ? this.focus.DOMNode : null;
1562 this.invoke = function moveCaretToDOMPoint_invoke()
1563 {
1564 if (this.focusNode)
1565 this.focusNode.focus();
1567 var selection = this.DOMPointNode.ownerDocument.defaultView.getSelection();
1568 var selRange = selection.getRangeAt(0);
1569 selRange.setStart(this.DOMPointNode, aDOMPointOffset);
1570 selRange.collapse(true);
1572 selection.removeRange(selRange);
1573 selection.addRange(selRange);
1574 }
1576 this.getID = function moveCaretToDOMPoint_getID()
1577 {
1578 return "Set caret on " + prettyName(aID) + " at point: " +
1579 prettyName(aDOMPointNodeID) + " node with offset " + aDOMPointOffset;
1580 }
1582 this.finalCheck = function moveCaretToDOMPoint_finalCheck()
1583 {
1584 if (aCheckFunc)
1585 aCheckFunc.call();
1586 }
1588 this.eventSeq = [
1589 new caretMoveChecker(aExpectedOffset, this.target)
1590 ];
1592 if (this.focus)
1593 this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
1594 }
1596 /**
1597 * Set caret offset in text accessible.
1598 */
1599 function setCaretOffset(aID, aOffset, aFocusTargetID)
1600 {
1601 this.target = getAccessible(aID, [nsIAccessibleText]);
1602 this.offset = aOffset == -1 ? this.target.characterCount: aOffset;
1603 this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
1605 this.invoke = function setCaretOffset_invoke()
1606 {
1607 this.target.caretOffset = this.offset;
1608 }
1610 this.getID = function setCaretOffset_getID()
1611 {
1612 return "Set caretOffset on " + prettyName(aID) + " at " + this.offset;
1613 }
1615 this.eventSeq = [
1616 new caretMoveChecker(this.offset, this.target)
1617 ];
1619 if (this.focus)
1620 this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
1621 }
1624 ////////////////////////////////////////////////////////////////////////////////
1625 // Event queue checkers
1627 /**
1628 * Common invoker checker (see eventSeq of eventQueue).
1629 */
1630 function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync)
1631 {
1632 this.type = aEventType;
1633 this.async = aIsAsync;
1635 this.__defineGetter__("target", invokerChecker_targetGetter);
1636 this.__defineSetter__("target", invokerChecker_targetSetter);
1638 // implementation details
1639 function invokerChecker_targetGetter()
1640 {
1641 if (typeof this.mTarget == "function")
1642 return this.mTarget.call(null, this.mTargetFuncArg);
1643 if (typeof this.mTarget == "string")
1644 return getNode(this.mTarget);
1646 return this.mTarget;
1647 }
1649 function invokerChecker_targetSetter(aValue)
1650 {
1651 this.mTarget = aValue;
1652 return this.mTarget;
1653 }
1655 this.__defineGetter__("targetDescr", invokerChecker_targetDescrGetter);
1657 function invokerChecker_targetDescrGetter()
1658 {
1659 if (typeof this.mTarget == "function")
1660 return this.mTarget.name + ", arg: " + this.mTargetFuncArg;
1662 return prettyName(this.mTarget);
1663 }
1665 this.mTarget = aTargetOrFunc;
1666 this.mTargetFuncArg = aTargetFuncArg;
1667 }
1669 /**
1670 * Generic invoker checker for unexpected events.
1671 */
1672 function unexpectedInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
1673 {
1674 this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
1675 aTargetFuncArg, true);
1677 this.unexpected = true;
1678 }
1680 /**
1681 * Common invoker checker for async events.
1682 */
1683 function asyncInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
1684 {
1685 this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
1686 aTargetFuncArg, true);
1687 }
1689 function focusChecker(aTargetOrFunc, aTargetFuncArg)
1690 {
1691 this.__proto__ = new invokerChecker(EVENT_FOCUS, aTargetOrFunc,
1692 aTargetFuncArg, false);
1694 this.unique = true; // focus event must be unique for invoker action
1696 this.check = function focusChecker_check(aEvent)
1697 {
1698 testStates(aEvent.accessible, STATE_FOCUSED);
1699 }
1700 }
1702 function nofocusChecker(aID)
1703 {
1704 this.__proto__ = new focusChecker(aID);
1705 this.unexpected = true;
1706 }
1708 /**
1709 * Text inserted/removed events checker.
1710 * @param aFromUser [in, optional] kNotFromUserInput or kFromUserInput
1711 */
1712 function textChangeChecker(aID, aStart, aEnd, aTextOrFunc, aIsInserted, aFromUser)
1713 {
1714 this.target = getNode(aID);
1715 this.type = aIsInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
1716 this.startOffset = aStart;
1717 this.endOffset = aEnd;
1718 this.textOrFunc = aTextOrFunc;
1720 this.check = function textChangeChecker_check(aEvent)
1721 {
1722 aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
1724 var modifiedText = (typeof this.textOrFunc == "function") ?
1725 this.textOrFunc() : this.textOrFunc;
1726 var modifiedTextLen =
1727 (this.endOffset == -1) ? modifiedText.length : aEnd - aStart;
1729 is(aEvent.start, this.startOffset,
1730 "Wrong start offset for " + prettyName(aID));
1731 is(aEvent.length, modifiedTextLen, "Wrong length for " + prettyName(aID));
1732 var changeInfo = (aIsInserted ? "inserted" : "removed");
1733 is(aEvent.isInserted, aIsInserted,
1734 "Text was " + changeInfo + " for " + prettyName(aID));
1735 is(aEvent.modifiedText, modifiedText,
1736 "Wrong " + changeInfo + " text for " + prettyName(aID));
1737 if (typeof aFromUser != "undefined")
1738 is(aEvent.isFromUserInput, aFromUser,
1739 "wrong value of isFromUserInput() for " + prettyName(aID));
1740 }
1741 }
1743 /**
1744 * Caret move events checker.
1745 */
1746 function caretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg)
1747 {
1748 this.__proto__ = new invokerChecker(EVENT_TEXT_CARET_MOVED,
1749 aTargetOrFunc, aTargetFuncArg);
1751 this.check = function caretMoveChecker_check(aEvent)
1752 {
1753 is(aEvent.QueryInterface(nsIAccessibleCaretMoveEvent).caretOffset,
1754 aCaretOffset,
1755 "Wrong caret offset for " + prettyName(aEvent.accessible));
1756 }
1757 }
1759 /**
1760 * Text selection change checker.
1761 */
1762 function textSelectionChecker(aID, aStartOffset, aEndOffset)
1763 {
1764 this.__proto__ = new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID);
1766 this.check = function textSelectionChecker_check(aEvent)
1767 {
1768 if (aStartOffset == aEndOffset) {
1769 is(getAccessible(aID, [nsIAccessibleText]).caretOffset, aStartOffset,
1770 "Wrong collapsed selection!");
1771 } else {
1772 testTextGetSelection(aID, aStartOffset, aEndOffset, 0);
1773 }
1774 }
1775 }
1777 /**
1778 * State change checker.
1779 */
1780 function stateChangeChecker(aState, aIsExtraState, aIsEnabled,
1781 aTargetOrFunc, aTargetFuncArg, aIsAsync,
1782 aSkipCurrentStateCheck)
1783 {
1784 this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
1785 aTargetFuncArg, aIsAsync);
1787 this.check = function stateChangeChecker_check(aEvent)
1788 {
1789 var event = null;
1790 try {
1791 var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
1792 } catch (e) {
1793 ok(false, "State change event was expected");
1794 }
1796 if (!event)
1797 return;
1799 is(event.isExtraState, aIsExtraState,
1800 "Wrong extra state bit of the statechange event.");
1801 isState(event.state, aState, aIsExtraState,
1802 "Wrong state of the statechange event.");
1803 is(event.isEnabled, aIsEnabled,
1804 "Wrong state of statechange event state");
1806 if (aSkipCurrentStateCheck) {
1807 todo(false, "State checking was skipped!");
1808 return;
1809 }
1811 var state = aIsEnabled ? (aIsExtraState ? 0 : aState) : 0;
1812 var extraState = aIsEnabled ? (aIsExtraState ? aState : 0) : 0;
1813 var unxpdState = aIsEnabled ? 0 : (aIsExtraState ? 0 : aState);
1814 var unxpdExtraState = aIsEnabled ? 0 : (aIsExtraState ? aState : 0);
1815 testStates(event.accessible, state, extraState, unxpdState, unxpdExtraState);
1816 }
1818 this.match = function stateChangeChecker_match(aEvent)
1819 {
1820 if (aEvent instanceof nsIAccessibleStateChangeEvent) {
1821 var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
1822 return (aEvent.accessible == getAccessible(this.target)) &&
1823 (scEvent.state == aState);
1824 }
1825 return false;
1826 }
1827 }
1829 function asyncStateChangeChecker(aState, aIsExtraState, aIsEnabled,
1830 aTargetOrFunc, aTargetFuncArg)
1831 {
1832 this.__proto__ = new stateChangeChecker(aState, aIsExtraState, aIsEnabled,
1833 aTargetOrFunc, aTargetFuncArg, true);
1834 }
1836 /**
1837 * Expanded state change checker.
1838 */
1839 function expandedStateChecker(aIsEnabled, aTargetOrFunc, aTargetFuncArg)
1840 {
1841 this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
1842 aTargetFuncArg);
1844 this.check = function expandedStateChecker_check(aEvent)
1845 {
1846 var event = null;
1847 try {
1848 var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
1849 } catch (e) {
1850 ok(false, "State change event was expected");
1851 }
1853 if (!event)
1854 return;
1856 is(event.state, STATE_EXPANDED, "Wrong state of the statechange event.");
1857 is(event.isExtraState, false,
1858 "Wrong extra state bit of the statechange event.");
1859 is(event.isEnabled, aIsEnabled,
1860 "Wrong state of statechange event state");
1862 testStates(event.accessible,
1863 (aIsEnabled ? STATE_EXPANDED : STATE_COLLAPSED));
1864 }
1865 }
1867 ////////////////////////////////////////////////////////////////////////////////
1868 // Event sequances (array of predefined checkers)
1870 /**
1871 * Event seq for single selection change.
1872 */
1873 function selChangeSeq(aUnselectedID, aSelectedID)
1874 {
1875 if (!aUnselectedID) {
1876 return [
1877 new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
1878 new invokerChecker(EVENT_SELECTION, aSelectedID)
1879 ];
1880 }
1882 // Return two possible scenarios: depending on widget type when selection is
1883 // moved the the order of items that get selected and unselected may vary.
1884 return [
1885 [
1886 new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
1887 new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
1888 new invokerChecker(EVENT_SELECTION, aSelectedID)
1889 ],
1890 [
1891 new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
1892 new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
1893 new invokerChecker(EVENT_SELECTION, aSelectedID)
1894 ]
1895 ];
1896 }
1898 /**
1899 * Event seq for item removed form the selection.
1900 */
1901 function selRemoveSeq(aUnselectedID)
1902 {
1903 return [
1904 new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
1905 new invokerChecker(EVENT_SELECTION_REMOVE, aUnselectedID)
1906 ];
1907 }
1909 /**
1910 * Event seq for item added to the selection.
1911 */
1912 function selAddSeq(aSelectedID)
1913 {
1914 return [
1915 new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
1916 new invokerChecker(EVENT_SELECTION_ADD, aSelectedID)
1917 ];
1918 }
1920 ////////////////////////////////////////////////////////////////////////////////
1921 // Private implementation details.
1922 ////////////////////////////////////////////////////////////////////////////////
1925 ////////////////////////////////////////////////////////////////////////////////
1926 // General
1928 var gA11yEventListeners = {};
1929 var gA11yEventApplicantsCount = 0;
1931 var gA11yEventObserver =
1932 {
1933 observe: function observe(aSubject, aTopic, aData)
1934 {
1935 if (aTopic != "accessible-event")
1936 return;
1938 var event;
1939 try {
1940 event = aSubject.QueryInterface(nsIAccessibleEvent);
1941 } catch (ex) {
1942 // After a test is aborted (i.e. timed out by the harness), this exception is soon triggered.
1943 // Remove the leftover observer, otherwise it "leaks" to all the following tests.
1944 Services.obs.removeObserver(this, "accessible-event");
1945 // Forward the exception, with added explanation.
1946 throw "[accessible/events.js, gA11yEventObserver.observe] This is expected if a previous test has been aborted... Initial exception was: [ " + ex + " ]";
1947 }
1948 var listenersArray = gA11yEventListeners[event.eventType];
1950 var eventFromDumpArea = false;
1951 if (gLogger.isEnabled()) { // debug stuff
1952 eventFromDumpArea = true;
1954 var target = event.DOMNode;
1955 var dumpElm = gA11yEventDumpID ?
1956 document.getElementById(gA11yEventDumpID) : null;
1958 if (dumpElm) {
1959 var parent = target;
1960 while (parent && parent != dumpElm)
1961 parent = parent.parentNode;
1962 }
1964 if (!dumpElm || parent != dumpElm) {
1965 var type = eventTypeToString(event.eventType);
1966 var info = "Event type: " + type;
1968 if (event instanceof nsIAccessibleStateChangeEvent) {
1969 var stateStr = statesToString(event.isExtraState ? 0 : event.state,
1970 event.isExtraState ? event.state : 0);
1971 info += ", state: " + stateStr + ", is enabled: " + event.isEnabled;
1973 } else if (event instanceof nsIAccessibleTextChangeEvent) {
1974 info += ", start: " + event.start + ", length: " + event.length +
1975 ", " + (event.isInserted ? "inserted" : "removed") +
1976 " text: " + event.modifiedText;
1977 }
1979 info += ". Target: " + prettyName(event.accessible);
1981 if (listenersArray)
1982 info += ". Listeners count: " + listenersArray.length;
1984 if (gLogger.hasFeature("parentchain:" + type)) {
1985 info += "\nParent chain:\n";
1986 var acc = event.accessible;
1987 while (acc) {
1988 info += " " + prettyName(acc) + "\n";
1989 acc = acc.parent;
1990 }
1991 }
1993 eventFromDumpArea = false;
1994 gLogger.log(info);
1995 }
1996 }
1998 // Do not notify listeners if event is result of event log changes.
1999 if (!listenersArray || eventFromDumpArea)
2000 return;
2002 for (var index = 0; index < listenersArray.length; index++)
2003 listenersArray[index].handleEvent(event);
2004 }
2005 };
2007 function listenA11yEvents(aStartToListen)
2008 {
2009 if (aStartToListen) {
2010 // Add observer when adding the first applicant only.
2011 if (!(gA11yEventApplicantsCount++))
2012 Services.obs.addObserver(gA11yEventObserver, "accessible-event", false);
2013 } else {
2014 // Remove observer when there are no more applicants only.
2015 // '< 0' case should not happen, but just in case: removeObserver() will throw.
2016 if (--gA11yEventApplicantsCount <= 0)
2017 Services.obs.removeObserver(gA11yEventObserver, "accessible-event");
2018 }
2019 }
2021 function addA11yEventListener(aEventType, aEventHandler)
2022 {
2023 if (!(aEventType in gA11yEventListeners))
2024 gA11yEventListeners[aEventType] = new Array();
2026 var listenersArray = gA11yEventListeners[aEventType];
2027 var index = listenersArray.indexOf(aEventHandler);
2028 if (index == -1)
2029 listenersArray.push(aEventHandler);
2030 }
2032 function removeA11yEventListener(aEventType, aEventHandler)
2033 {
2034 var listenersArray = gA11yEventListeners[aEventType];
2035 if (!listenersArray)
2036 return false;
2038 var index = listenersArray.indexOf(aEventHandler);
2039 if (index == -1)
2040 return false;
2042 listenersArray.splice(index, 1);
2044 if (!listenersArray.length) {
2045 gA11yEventListeners[aEventType] = null;
2046 delete gA11yEventListeners[aEventType];
2047 }
2049 return true;
2050 }
2052 /**
2053 * Used to dump debug information.
2054 */
2055 var gLogger =
2056 {
2057 /**
2058 * Return true if dump is enabled.
2059 */
2060 isEnabled: function debugOutput_isEnabled()
2061 {
2062 return gA11yEventDumpID || gA11yEventDumpToConsole ||
2063 gA11yEventDumpToAppConsole;
2064 },
2066 /**
2067 * Dump information into DOM and console if applicable.
2068 */
2069 log: function logger_log(aMsg)
2070 {
2071 this.logToConsole(aMsg);
2072 this.logToAppConsole(aMsg);
2073 this.logToDOM(aMsg);
2074 },
2076 /**
2077 * Log message to DOM.
2078 *
2079 * @param aMsg [in] the primary message
2080 * @param aHasIndent [in, optional] if specified the message has an indent
2081 * @param aPreEmphText [in, optional] the text is colored and appended prior
2082 * primary message
2083 */
2084 logToDOM: function logger_logToDOM(aMsg, aHasIndent, aPreEmphText)
2085 {
2086 if (gA11yEventDumpID == "")
2087 return;
2089 var dumpElm = document.getElementById(gA11yEventDumpID);
2090 if (!dumpElm) {
2091 ok(false,
2092 "No dump element '" + gA11yEventDumpID + "' within the document!");
2093 return;
2094 }
2096 var containerTagName = document instanceof nsIDOMHTMLDocument ?
2097 "div" : "description";
2099 var container = document.createElement(containerTagName);
2100 if (aHasIndent)
2101 container.setAttribute("style", "padding-left: 10px;");
2103 if (aPreEmphText) {
2104 var inlineTagName = document instanceof nsIDOMHTMLDocument ?
2105 "span" : "description";
2106 var emphElm = document.createElement(inlineTagName);
2107 emphElm.setAttribute("style", "color: blue;");
2108 emphElm.textContent = aPreEmphText;
2110 container.appendChild(emphElm);
2111 }
2113 var textNode = document.createTextNode(aMsg);
2114 container.appendChild(textNode);
2116 dumpElm.appendChild(container);
2117 },
2119 /**
2120 * Log message to console.
2121 */
2122 logToConsole: function logger_logToConsole(aMsg)
2123 {
2124 if (gA11yEventDumpToConsole)
2125 dump("\n" + aMsg + "\n");
2126 },
2128 /**
2129 * Log message to error console.
2130 */
2131 logToAppConsole: function logger_logToAppConsole(aMsg)
2132 {
2133 if (gA11yEventDumpToAppConsole)
2134 Services.console.logStringMessage("events: " + aMsg);
2135 },
2137 /**
2138 * Return true if logging feature is enabled.
2139 */
2140 hasFeature: function logger_hasFeature(aFeature)
2141 {
2142 var startIdx = gA11yEventDumpFeature.indexOf(aFeature);
2143 if (startIdx == - 1)
2144 return false;
2146 var endIdx = startIdx + aFeature.length;
2147 return endIdx == gA11yEventDumpFeature.length ||
2148 gA11yEventDumpFeature[endIdx] == ";";
2149 }
2150 };
2153 ////////////////////////////////////////////////////////////////////////////////
2154 // Sequence
2156 /**
2157 * Base class of sequence item.
2158 */
2159 function sequenceItem(aProcessor, aEventType, aTarget, aItemID)
2160 {
2161 // private
2163 this.startProcess = function sequenceItem_startProcess()
2164 {
2165 this.queue.invoke();
2166 }
2168 var item = this;
2170 this.queue = new eventQueue();
2171 this.queue.onFinish = function()
2172 {
2173 aProcessor.onProcessed();
2174 return DO_NOT_FINISH_TEST;
2175 }
2177 var invoker = {
2178 invoke: function invoker_invoke() {
2179 return aProcessor.process();
2180 },
2181 getID: function invoker_getID()
2182 {
2183 return aItemID;
2184 },
2185 eventSeq: [ new invokerChecker(aEventType, aTarget) ]
2186 };
2188 this.queue.push(invoker);
2189 }
2191 ////////////////////////////////////////////////////////////////////////////////
2192 // Event queue invokers
2194 /**
2195 * Invoker base class for prepare an action.
2196 */
2197 function synthAction(aNodeOrID, aEventsObj)
2198 {
2199 this.DOMNode = getNode(aNodeOrID);
2201 if (aEventsObj) {
2202 var scenarios = null;
2203 if (aEventsObj instanceof Array) {
2204 if (aEventsObj[0] instanceof Array)
2205 scenarios = aEventsObj; // scenarios
2206 else
2207 scenarios = [ aEventsObj ]; // event sequance
2208 } else {
2209 scenarios = [ [ aEventsObj ] ]; // a single checker object
2210 }
2212 for (var i = 0; i < scenarios.length; i++)
2213 defineScenario(this, scenarios[i]);
2214 }
2216 this.getID = function synthAction_getID()
2217 { return prettyName(aNodeOrID) + " action"; }
2218 }