1 /* import-globals-from common.js */
2 /* import-globals-from states.js */
3 /* import-globals-from text.js */
5 // XXX Bug 1425371 - enable no-redeclare and fix the issues with the tests.
6 /* eslint-disable no-redeclare */
8 // //////////////////////////////////////////////////////////////////////////////
11 const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT;
12 const EVENT_ANNOUNCEMENT = nsIAccessibleEvent.EVENT_ANNOUNCEMENT;
13 const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
14 const EVENT_DOCUMENT_LOAD_COMPLETE =
15 nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
16 const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD;
17 const EVENT_DOCUMENT_LOAD_STOPPED =
18 nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_STOPPED;
19 const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
20 const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
21 const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
22 const EVENT_MENU_START = nsIAccessibleEvent.EVENT_MENU_START;
23 const EVENT_MENU_END = nsIAccessibleEvent.EVENT_MENU_END;
24 const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START;
25 const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END;
26 const EVENT_OBJECT_ATTRIBUTE_CHANGED =
27 nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED;
28 const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
29 const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START;
30 const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION;
31 const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD;
32 const EVENT_SELECTION_REMOVE = nsIAccessibleEvent.EVENT_SELECTION_REMOVE;
33 const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
34 const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
35 const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
36 const EVENT_TEXT_ATTRIBUTE_CHANGED =
37 nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED;
38 const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
39 const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
40 const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
41 const EVENT_TEXT_SELECTION_CHANGED =
42 nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED;
43 const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
44 const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE;
45 const EVENT_VIRTUALCURSOR_CHANGED =
46 nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED;
48 const kNotFromUserInput = 0;
49 const kFromUserInput = 1;
51 // //////////////////////////////////////////////////////////////////////////////
55 * Set up this variable to dump events into DOM.
57 var gA11yEventDumpID = "";
60 * Set up this variable to dump event processing into console.
62 var gA11yEventDumpToConsole = false;
65 * Set up this variable to dump event processing into error console.
67 var gA11yEventDumpToAppConsole = false;
70 * Semicolon separated set of logging features.
72 var gA11yEventDumpFeature = "";
75 * Function to detect HTML elements when given a node.
77 function isHTMLElement(aNode) {
79 aNode.nodeType == aNode.ELEMENT_NODE &&
80 aNode.namespaceURI == "http://www.w3.org/1999/xhtml"
84 function isXULElement(aNode) {
86 aNode.nodeType == aNode.ELEMENT_NODE &&
88 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
93 * Executes the function when requested event is handled.
95 * @param aEventType [in] event type
96 * @param aTarget [in] event target
97 * @param aFunc [in] function to call when event is handled
98 * @param aContext [in, optional] object in which context the function is
100 * @param aArg1 [in, optional] argument passed into the function
101 * @param aArg2 [in, optional] argument passed into the function
103 function waitForEvent(
112 handleEvent: function handleEvent(aEvent) {
113 var target = aTargetOrFunc;
114 if (typeof aTargetOrFunc == "function") {
115 target = aTargetOrFunc.call();
119 if (target instanceof nsIAccessible && target != aEvent.accessible) {
123 if (Node.isInstance(target) && target != aEvent.DOMNode) {
128 unregisterA11yEventListener(aEventType, this);
130 window.setTimeout(function () {
131 aFunc.call(aContext, aArg1, aArg2);
136 registerA11yEventListener(aEventType, handler);
140 * Generate mouse move over image map what creates image map accessible (async).
141 * See waitForImageMap() function.
143 function waveOverImageMap(aImageMapID) {
144 var imageMapNode = getNode(aImageMapID);
149 { type: "mousemove" },
150 imageMapNode.ownerGlobal
155 * Call the given function when the tree of the given image map is built.
157 function waitForImageMap(aImageMapID, aTestFunc) {
158 waveOverImageMap(aImageMapID);
160 var imageMapAcc = getAccessible(aImageMapID);
161 if (imageMapAcc.firstChild) {
166 waitForEvent(EVENT_REORDER, imageMapAcc, aTestFunc);
170 * Register accessibility event listener.
172 * @param aEventType the accessible event type (see nsIAccessibleEvent for
173 * available constants).
174 * @param aEventHandler event listener object, when accessible event of the
175 * given type is handled then 'handleEvent' method of
176 * this object is invoked with nsIAccessibleEvent object
177 * as the first argument.
179 function registerA11yEventListener(aEventType, aEventHandler) {
180 listenA11yEvents(true);
181 addA11yEventListener(aEventType, aEventHandler);
185 * Unregister accessibility event listener. Must be called for every registered
186 * event listener (see registerA11yEventListener() function) when the listener
189 function unregisterA11yEventListener(aEventType, aEventHandler) {
190 removeA11yEventListener(aEventType, aEventHandler);
191 listenA11yEvents(false);
194 // //////////////////////////////////////////////////////////////////////////////
198 * Return value of invoke method of invoker object. Indicates invoker was unable
201 const INVOKER_ACTION_FAILED = 1;
204 * Return value of eventQueue.onFinish. Indicates eventQueue should not finish
207 const DO_NOT_FINISH_TEST = 1;
210 * Creates event queue for the given event type. The queue consists of invoker
211 * objects, each of them generates the event of the event type. When queue is
212 * started then every invoker object is asked to generate event after timeout.
213 * When event is caught then current invoker object is asked to check whether
214 * event was handled correctly.
216 * Invoker interface is:
219 * // Generates accessible event or event sequence. If returns
220 * // INVOKER_ACTION_FAILED constant then stop tests.
221 * invoke: function(){},
223 * // [optional] Invoker's check of handled event for correctness.
224 * check: function(aEvent){},
226 * // [optional] Invoker's check before the next invoker is proceeded.
227 * finalCheck: function(aEvent){},
229 * // [optional] Is called when event of any registered type is handled.
230 * debugCheck: function(aEvent){},
232 * // [ignored if 'eventSeq' is defined] DOM node event is generated for
233 * // (used in the case when invoker expects single event).
234 * DOMNode getter: function() {},
236 * // [optional] if true then event sequences are ignored (no failure if
237 * // sequences are empty). Use you need to invoke an action, do some check
238 * // after timeout and proceed a next invoker.
239 * noEventsOnAction getter: function() {},
241 * // Array of checker objects defining expected events on invoker's action.
243 * // Checker object interface:
246 * // * DOM or a11y event type. *
247 * // type getter: function() {},
249 * // * DOM node or accessible. *
250 * // target getter: function() {},
252 * // * DOM event phase (false - bubbling). *
253 * // phase getter: function() {},
255 * // * Callback, called to match handled event. *
256 * // match : function(aEvent) {},
258 * // * Callback, called when event is handled
259 * // check: function(aEvent) {},
262 * // getID: function() {},
264 * // * Event that don't have predefined order relative other events. *
265 * // async getter: function() {},
267 * // * Event that is not expected. *
268 * // unexpected getter: function() {},
270 * // * No other event of the same type is not allowed. *
271 * // unique getter: function() {}
273 * eventSeq getter() {},
275 * // Array of checker objects defining unexpected events on invoker's
277 * unexpectedEventSeq getter() {},
279 * // The ID of invoker.
280 * getID: function(){} // returns invoker ID
283 * // Used to add a possible scenario of expected/unexpected events on
284 * // invoker's action.
285 * defineScenario(aInvokerObj, aEventSeq, aUnexpectedEventSeq)
288 * @param aEventType [in, optional] the default event type (isn't used if
289 * invoker defines eventSeq property).
291 function eventQueue(aEventType) {
295 * Add invoker object into queue.
297 this.push = function eventQueue_push(aEventInvoker) {
298 this.mInvokers.push(aEventInvoker);
302 * Start the queue processing.
304 this.invoke = function eventQueue_invoke() {
305 listenA11yEvents(true);
307 // XXX: Intermittent test_events_caretmove.html fails withouth timeout,
309 this.processNextInvokerInTimeout(true);
313 * This function is called when all events in the queue were handled.
314 * Override it if you need to be notified of this.
316 this.onFinish = function eventQueue_finish() {};
321 * Process next invoker.
323 // eslint-disable-next-line complexity
324 this.processNextInvoker = function eventQueue_processNextInvoker() {
325 // Some scenario was matched, we wait on next invoker processing.
326 if (this.mNextInvokerStatus == kInvokerCanceled) {
327 this.setInvokerStatus(
328 kInvokerNotScheduled,
329 "scenario was matched, wait for next invoker activation"
334 this.setInvokerStatus(
335 kInvokerNotScheduled,
336 "the next invoker is processed now"
339 // Finish processing of the current invoker if any.
340 var testFailed = false;
342 var invoker = this.getInvoker();
344 if ("finalCheck" in invoker) {
345 invoker.finalCheck();
348 if (this.mScenarios && this.mScenarios.length) {
350 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
351 var eventSeq = this.mScenarios[scnIdx];
352 if (!this.areExpectedEventsLeft(eventSeq)) {
353 for (var idx = 0; idx < eventSeq.length; idx++) {
354 var checker = eventSeq[idx];
356 (checker.unexpected && checker.wasCaught) ||
357 (!checker.unexpected && checker.wasCaught != 1)
363 // Ok, we have matched scenario. Report it was completed ok. In
364 // case of empty scenario guess it was matched but if later we
365 // find out that non empty scenario was matched then it will be
367 if (idx == eventSeq.length) {
371 this.mScenarios[matchIdx].length
375 "We have a matched scenario at index " +
381 if (matchIdx == -1 || eventSeq.length) {
385 // Report everything is ok.
386 for (var idx = 0; idx < eventSeq.length; idx++) {
387 var checker = eventSeq[idx];
389 var typeStr = eventQueue.getEventTypeAsString(checker);
391 "Test with ID = '" + this.getEventID(checker) + "' succeed. ";
393 if (checker.unexpected) {
394 ok(true, msg + `There's no unexpected '${typeStr}' event.`);
395 } else if (checker.todo) {
396 todo(false, `Todo event '${typeStr}' was caught`);
398 ok(true, `${msg} Event '${typeStr}' was handled.`);
405 // We don't have completely matched scenario. Report each failure/success
406 // for every scenario.
407 if (matchIdx == -1) {
409 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
410 var eventSeq = this.mScenarios[scnIdx];
411 for (var idx = 0; idx < eventSeq.length; idx++) {
412 var checker = eventSeq[idx];
414 var typeStr = eventQueue.getEventTypeAsString(checker);
418 " of test with ID = '" +
419 this.getEventID(checker) +
422 if (checker.wasCaught > 1) {
423 ok(false, msg + "Dupe " + typeStr + " event.");
426 if (checker.unexpected) {
427 if (checker.wasCaught) {
428 ok(false, msg + "There's unexpected " + typeStr + " event.");
430 } else if (!checker.wasCaught) {
431 var rf = checker.todo ? todo : ok;
432 rf(false, `${msg} '${typeStr} event is missed.`);
440 this.clearEventHandler();
442 // Check if need to stop the test.
443 if (testFailed || this.mIndex == this.mInvokers.length - 1) {
444 listenA11yEvents(false);
446 var res = this.onFinish();
447 if (res != DO_NOT_FINISH_TEST) {
448 SimpleTest.executeSoon(SimpleTest.finish);
454 // Start processing of next invoker.
455 invoker = this.getNextInvoker();
457 // Set up event listeners. Process a next invoker if no events were added.
458 if (!this.setEventHandler(invoker)) {
459 this.processNextInvoker();
463 if (gLogger.isEnabled()) {
464 gLogger.logToConsole("Event queue: \n invoke: " + invoker.getID());
465 gLogger.logToDOM("EQ: invoke: " + invoker.getID(), true);
468 var infoText = "Invoke the '" + invoker.getID() + "' test { ";
469 var scnCount = this.mScenarios ? this.mScenarios.length : 0;
470 for (var scnIdx = 0; scnIdx < scnCount; scnIdx++) {
471 infoText += "scenario #" + scnIdx + ": ";
472 var eventSeq = this.mScenarios[scnIdx];
473 for (var idx = 0; idx < eventSeq.length; idx++) {
474 infoText += eventSeq[idx].unexpected
478 eventQueue.getEventTypeAsString(eventSeq[idx]) +
485 if (invoker.invoke() == INVOKER_ACTION_FAILED) {
486 // Invoker failed to prepare action, fail and finish tests.
487 this.processNextInvoker();
491 if (this.hasUnexpectedEventsScenario()) {
492 this.processNextInvokerInTimeout(true);
496 this.processNextInvokerInTimeout =
497 function eventQueue_processNextInvokerInTimeout(aUncondProcess) {
498 this.setInvokerStatus(kInvokerPending, "Process next invoker in timeout");
500 // No need to wait extra timeout when a) we know we don't need to do that
501 // and b) there's no any single unexpected event.
502 if (!aUncondProcess && this.areAllEventsExpected()) {
503 // We need delay to avoid events coalesce from different invokers.
505 SimpleTest.executeSoon(function () {
506 queue.processNextInvoker();
511 // Check in timeout invoker didn't fire registered events.
514 aQueue.processNextInvoker();
522 * Handle events for the current invoker.
524 // eslint-disable-next-line complexity
525 this.handleEvent = function eventQueue_handleEvent(aEvent) {
526 var invoker = this.getInvoker();
528 // skip events before test was started
532 if (!this.mScenarios) {
533 // Bad invoker object, error will be reported before processing of next
534 // invoker in the queue.
535 this.processNextInvoker();
539 if ("debugCheck" in invoker) {
540 invoker.debugCheck(aEvent);
543 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
544 var eventSeq = this.mScenarios[scnIdx];
545 for (var idx = 0; idx < eventSeq.length; idx++) {
546 var checker = eventSeq[idx];
548 // Search through handled expected events to report error if one of them
549 // is handled for a second time.
551 !checker.unexpected &&
552 checker.wasCaught > 0 &&
553 eventQueue.isSameEvent(checker, aEvent)
559 // Search through unexpected events, any match results in error report
560 // after this invoker processing (in case of matched scenario only).
561 if (checker.unexpected && eventQueue.compareEvents(checker, aEvent)) {
566 // Report an error if we handled not expected event of unique type
567 // (i.e. event types are matched, targets differs).
569 !checker.unexpected &&
571 eventQueue.compareEventTypes(checker, aEvent)
573 var isExpected = false;
574 for (var jdx = 0; jdx < eventSeq.length; jdx++) {
575 isExpected = eventQueue.compareEvents(eventSeq[jdx], aEvent);
585 eventQueue.getEventTypeAsString(checker) +
586 " event was handled."
593 var hasMatchedCheckers = false;
594 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
595 var eventSeq = this.mScenarios[scnIdx];
597 // Check if handled event matches expected sync event.
598 var nextChecker = this.getNextExpectedEvent(eventSeq);
600 if (eventQueue.compareEvents(nextChecker, aEvent)) {
601 this.processMatchedChecker(aEvent, nextChecker, scnIdx, eventSeq.idx);
602 hasMatchedCheckers = true;
607 // Check if handled event matches any expected async events.
608 var haveUnmatchedAsync = false;
609 for (idx = 0; idx < eventSeq.length; idx++) {
610 if (eventSeq[idx] instanceof orderChecker && haveUnmatchedAsync) {
614 if (!eventSeq[idx].wasCaught) {
615 haveUnmatchedAsync = true;
618 if (!eventSeq[idx].unexpected && eventSeq[idx].async) {
619 if (eventQueue.compareEvents(eventSeq[idx], aEvent)) {
620 this.processMatchedChecker(aEvent, eventSeq[idx], scnIdx, idx);
621 hasMatchedCheckers = true;
628 if (hasMatchedCheckers) {
629 var invoker = this.getInvoker();
630 if ("check" in invoker) {
631 invoker.check(aEvent);
635 for (idx = 0; idx < eventSeq.length; idx++) {
636 if (!eventSeq[idx].wasCaught) {
637 if (eventSeq[idx] instanceof orderChecker) {
638 eventSeq[idx].wasCaught++;
645 // If we don't have more events to wait then schedule next invoker.
646 if (this.hasMatchedScenario()) {
647 if (this.mNextInvokerStatus == kInvokerNotScheduled) {
648 this.processNextInvokerInTimeout();
649 } else if (this.mNextInvokerStatus == kInvokerCanceled) {
650 this.setInvokerStatus(
652 "Full match. Void the cancelation of next invoker processing"
658 // If we have scheduled a next invoker then cancel in case of match.
659 if (this.mNextInvokerStatus == kInvokerPending && hasMatchedCheckers) {
660 this.setInvokerStatus(
662 "Cancel the scheduled invoker in case of match"
668 this.processMatchedChecker = function eventQueue_function(
674 aMatchedChecker.wasCaught++;
676 if ("check" in aMatchedChecker) {
677 aMatchedChecker.check(aEvent);
685 this.areExpectedEventsLeft(),
686 this.mNextInvokerStatus
690 this.getNextExpectedEvent = function eventQueue_getNextExpectedEvent(
693 if (!("idx" in aEventSeq)) {
698 aEventSeq.idx < aEventSeq.length &&
699 (aEventSeq[aEventSeq.idx].unexpected ||
700 aEventSeq[aEventSeq.idx].todo ||
701 aEventSeq[aEventSeq.idx].async ||
702 aEventSeq[aEventSeq.idx] instanceof orderChecker ||
703 aEventSeq[aEventSeq.idx].wasCaught > 0)
708 return aEventSeq.idx != aEventSeq.length ? aEventSeq[aEventSeq.idx] : null;
711 this.areExpectedEventsLeft = function eventQueue_areExpectedEventsLeft(
714 function scenarioHasUnhandledExpectedEvent(aEventSeq) {
715 // Check if we have unhandled async (can be anywhere in the sequance) or
716 // sync expcected events yet.
717 for (var idx = 0; idx < aEventSeq.length; idx++) {
719 !aEventSeq[idx].unexpected &&
720 !aEventSeq[idx].todo &&
721 !aEventSeq[idx].wasCaught &&
722 !(aEventSeq[idx] instanceof orderChecker)
732 return scenarioHasUnhandledExpectedEvent(aScenario);
735 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
736 var eventSeq = this.mScenarios[scnIdx];
737 if (scenarioHasUnhandledExpectedEvent(eventSeq)) {
744 this.areAllEventsExpected = function eventQueue_areAllEventsExpected() {
745 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
746 var eventSeq = this.mScenarios[scnIdx];
747 for (var idx = 0; idx < eventSeq.length; idx++) {
748 if (eventSeq[idx].unexpected || eventSeq[idx].todo) {
757 this.isUnexpectedEventScenario =
758 function eventQueue_isUnexpectedEventsScenario(aScenario) {
759 for (var idx = 0; idx < aScenario.length; idx++) {
760 if (!aScenario[idx].unexpected && !aScenario[idx].todo) {
765 return idx == aScenario.length;
768 this.hasUnexpectedEventsScenario =
769 function eventQueue_hasUnexpectedEventsScenario() {
770 if (this.getInvoker().noEventsOnAction) {
774 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
775 if (this.isUnexpectedEventScenario(this.mScenarios[scnIdx])) {
783 this.hasMatchedScenario = function eventQueue_hasMatchedScenario() {
784 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
785 var scn = this.mScenarios[scnIdx];
787 !this.isUnexpectedEventScenario(scn) &&
788 !this.areExpectedEventsLeft(scn)
796 this.getInvoker = function eventQueue_getInvoker() {
797 return this.mInvokers[this.mIndex];
800 this.getNextInvoker = function eventQueue_getNextInvoker() {
801 return this.mInvokers[++this.mIndex];
804 this.setEventHandler = function eventQueue_setEventHandler(aInvoker) {
805 if (!("scenarios" in aInvoker) || !aInvoker.scenarios.length) {
806 var eventSeq = aInvoker.eventSeq;
807 var unexpectedEventSeq = aInvoker.unexpectedEventSeq;
808 if (!eventSeq && !unexpectedEventSeq && this.mDefEventType) {
809 eventSeq = [new invokerChecker(this.mDefEventType, aInvoker.DOMNode)];
812 if (eventSeq || unexpectedEventSeq) {
813 defineScenario(aInvoker, eventSeq, unexpectedEventSeq);
817 if (aInvoker.noEventsOnAction) {
821 this.mScenarios = aInvoker.scenarios;
822 if (!this.mScenarios || !this.mScenarios.length) {
823 ok(false, "Broken invoker '" + aInvoker.getID() + "'");
827 // Register event listeners.
828 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
829 var eventSeq = this.mScenarios[scnIdx];
831 if (gLogger.isEnabled()) {
835 ", registered events number: " +
837 gLogger.logToConsole(msg);
838 gLogger.logToDOM(msg, true);
841 // Do not warn about empty event sequances when more than one scenario
843 if (this.mScenarios.length == 1 && !eventSeq.length) {
846 "Broken scenario #" +
850 "'. No registered events"
855 for (var idx = 0; idx < eventSeq.length; idx++) {
856 eventSeq[idx].wasCaught = 0;
859 for (var idx = 0; idx < eventSeq.length; idx++) {
860 if (gLogger.isEnabled()) {
861 var msg = "registered";
862 if (eventSeq[idx].unexpected) {
863 msg += " unexpected";
865 if (eventSeq[idx].async) {
871 eventQueue.getEventTypeAsString(eventSeq[idx]) +
873 eventQueue.getEventTargetDescr(eventSeq[idx], true);
875 gLogger.logToConsole(msg);
876 gLogger.logToDOM(msg, true);
879 var eventType = eventSeq[idx].type;
880 if (typeof eventType == "string") {
882 var target = eventQueue.getEventTarget(eventSeq[idx]);
884 ok(false, "no target for DOM event!");
887 var phase = eventQueue.getEventPhase(eventSeq[idx]);
888 target.addEventListener(eventType, this, phase);
891 addA11yEventListener(eventType, this);
899 this.clearEventHandler = function eventQueue_clearEventHandler() {
900 if (!this.mScenarios) {
904 for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
905 var eventSeq = this.mScenarios[scnIdx];
906 for (var idx = 0; idx < eventSeq.length; idx++) {
907 var eventType = eventSeq[idx].type;
908 if (typeof eventType == "string") {
910 var target = eventQueue.getEventTarget(eventSeq[idx]);
911 var phase = eventQueue.getEventPhase(eventSeq[idx]);
912 target.removeEventListener(eventType, this, phase);
915 removeA11yEventListener(eventType, this);
919 this.mScenarios = null;
922 this.getEventID = function eventQueue_getEventID(aChecker) {
923 if ("getID" in aChecker) {
924 return aChecker.getID();
927 var invoker = this.getInvoker();
928 return invoker.getID();
931 this.setInvokerStatus = function eventQueue_setInvokerStatus(
935 this.mNextInvokerStatus = aStatus;
937 // Uncomment it to debug invoker processing logic.
938 // gLogger.log(eventQueue.invokerStatusToMsg(aStatus, aLogMsg));
941 this.mDefEventType = aEventType;
945 this.mScenarios = null;
947 this.mNextInvokerStatus = kInvokerNotScheduled;
950 // //////////////////////////////////////////////////////////////////////////////
951 // eventQueue static members and constants
953 const kInvokerNotScheduled = 0;
954 const kInvokerPending = 1;
955 const kInvokerCanceled = 2;
957 eventQueue.getEventTypeAsString = function eventQueue_getEventTypeAsString(
960 if (Event.isInstance(aEventOrChecker)) {
961 return aEventOrChecker.type;
964 if (aEventOrChecker instanceof nsIAccessibleEvent) {
965 return eventTypeToString(aEventOrChecker.eventType);
968 return typeof aEventOrChecker.type == "string"
969 ? aEventOrChecker.type
970 : eventTypeToString(aEventOrChecker.type);
973 eventQueue.getEventTargetDescr = function eventQueue_getEventTargetDescr(
977 if (Event.isInstance(aEventOrChecker)) {
978 return prettyName(aEventOrChecker.originalTarget);
981 // XXXbz this block doesn't seem to be reachable...
982 if (Event.isInstance(aEventOrChecker)) {
983 return prettyName(aEventOrChecker.accessible);
986 var descr = aEventOrChecker.targetDescr;
991 if (aDontForceTarget) {
992 return "no target description";
995 var target = "target" in aEventOrChecker ? aEventOrChecker.target : null;
996 return prettyName(target);
999 eventQueue.getEventPhase = function eventQueue_getEventPhase(aChecker) {
1000 return "phase" in aChecker ? aChecker.phase : true;
1003 eventQueue.getEventTarget = function eventQueue_getEventTarget(aChecker) {
1004 if ("eventTarget" in aChecker) {
1005 switch (aChecker.eventTarget) {
1007 return aChecker.target;
1010 return aChecker.target.ownerDocument;
1013 return aChecker.target.ownerDocument;
1016 eventQueue.compareEventTypes = function eventQueue_compareEventTypes(
1020 var eventType = Event.isInstance(aEvent) ? aEvent.type : aEvent.eventType;
1021 return aChecker.type == eventType;
1024 eventQueue.compareEvents = function eventQueue_compareEvents(aChecker, aEvent) {
1025 if (!eventQueue.compareEventTypes(aChecker, aEvent)) {
1029 // If checker provides "match" function then allow the checker to decide
1030 // whether event is matched.
1031 if ("match" in aChecker) {
1032 return aChecker.match(aEvent);
1035 var target1 = aChecker.target;
1036 if (target1 instanceof nsIAccessible) {
1037 var target2 = Event.isInstance(aEvent)
1038 ? getAccessible(aEvent.target)
1039 : aEvent.accessible;
1041 return target1 == target2;
1044 // If original target isn't suitable then extend interface to support target
1045 // (original target is used in test_elm_media.html).
1046 var target2 = Event.isInstance(aEvent)
1047 ? aEvent.originalTarget
1049 return target1 == target2;
1052 eventQueue.isSameEvent = function eventQueue_isSameEvent(aChecker, aEvent) {
1053 // We don't have stored info about handled event other than its type and
1054 // target, thus we should filter text change and state change events since
1055 // they may occur on the same element because of complex changes.
1057 this.compareEvents(aChecker, aEvent) &&
1058 !(aEvent instanceof nsIAccessibleTextChangeEvent) &&
1059 !(aEvent instanceof nsIAccessibleStateChangeEvent)
1063 eventQueue.invokerStatusToMsg = function eventQueue_invokerStatusToMsg(
1067 var msg = "invoker status: ";
1068 switch (aInvokerStatus) {
1069 case kInvokerNotScheduled:
1070 msg += "not scheduled";
1072 case kInvokerPending:
1075 case kInvokerCanceled:
1081 msg += " (" + aMsg + ")";
1087 eventQueue.logEvent = function eventQueue_logEvent(
1092 aAreExpectedEventsLeft,
1095 // Dump DOM event information. Skip a11y event since it is dumped by
1096 // gA11yEventObserver.
1097 if (Event.isInstance(aOrigEvent)) {
1098 var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent);
1099 info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent);
1100 gLogger.logToDOM(info);
1104 "unhandled expected events: " +
1105 aAreExpectedEventsLeft +
1107 eventQueue.invokerStatusToMsg(aInvokerStatus);
1109 var currType = eventQueue.getEventTypeAsString(aMatchedChecker);
1110 var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker);
1112 "*****\nScenario " +
1121 gLogger.logToConsole(consoleMsg);
1123 var emphText = "matched ";
1125 "EQ event, type: " +
1131 gLogger.logToDOM(msg, true, emphText);
1134 // //////////////////////////////////////////////////////////////////////////////
1138 * Deal with action sequence. Used when you need to execute couple of actions
1139 * each after other one.
1141 function sequence() {
1143 * Append new sequence item.
1145 * @param aProcessor [in] object implementing interface
1147 * // execute item action
1148 * process: function() {},
1149 * // callback, is called when item was processed
1150 * onProcessed: function() {}
1152 * @param aEventType [in] event type of expected event on item action
1153 * @param aTarget [in] event target of expected event on item action
1154 * @param aItemID [in] identifier of item
1156 this.append = function sequence_append(
1162 var item = new sequenceItem(aProcessor, aEventType, aTarget, aItemID);
1163 this.items.push(item);
1167 * Process next sequence item.
1169 this.processNext = function sequence_processNext() {
1171 if (this.idx >= this.items.length) {
1172 ok(false, "End of sequence: nothing to process!");
1173 SimpleTest.finish();
1177 this.items[this.idx].startProcess();
1184 // //////////////////////////////////////////////////////////////////////////////
1185 // Event queue invokers
1188 * Defines a scenario of expected/unexpected events. Each invoker can have
1189 * one or more scenarios of events. Only one scenario must be completed.
1191 function defineScenario(aInvoker, aEventSeq, aUnexpectedEventSeq) {
1192 if (!("scenarios" in aInvoker)) {
1193 aInvoker.scenarios = [];
1196 // Create unified event sequence concatenating expected and unexpected
1202 for (var idx = 0; idx < aEventSeq.length; idx++) {
1203 aEventSeq[idx].unexpected |= false;
1204 aEventSeq[idx].async |= false;
1207 if (aUnexpectedEventSeq) {
1208 for (var idx = 0; idx < aUnexpectedEventSeq.length; idx++) {
1209 aUnexpectedEventSeq[idx].unexpected = true;
1210 aUnexpectedEventSeq[idx].async = false;
1213 aEventSeq = aEventSeq.concat(aUnexpectedEventSeq);
1216 aInvoker.scenarios.push(aEventSeq);
1220 * Invokers defined below take a checker object (or array of checker objects).
1221 * An invoker listens for default event type registered in event queue object
1222 * until its checker is provided.
1224 * Note, checker object or array of checker objects is optional.
1230 function synthClick(aNodeOrID, aCheckerOrEventSeq, aArgs) {
1231 this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
1233 this.invoke = function synthClick_invoke() {
1234 var targetNode = this.DOMNode;
1235 if (targetNode.nodeType == targetNode.DOCUMENT_NODE) {
1236 targetNode = this.DOMNode.body
1238 : this.DOMNode.documentElement;
1241 // Scroll the node into view, otherwise synth click may fail.
1242 if (isHTMLElement(targetNode)) {
1243 targetNode.scrollIntoView(true);
1244 } else if (isXULElement(targetNode)) {
1245 var targetAcc = getAccessible(targetNode);
1246 targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
1251 if (aArgs && "where" in aArgs) {
1252 if (aArgs.where == "right") {
1253 if (isHTMLElement(targetNode)) {
1254 x = targetNode.offsetWidth - 1;
1255 } else if (isXULElement(targetNode)) {
1256 x = targetNode.getBoundingClientRect().width - 1;
1258 } else if (aArgs.where == "center") {
1259 if (isHTMLElement(targetNode)) {
1260 x = targetNode.offsetWidth / 2;
1261 y = targetNode.offsetHeight / 2;
1262 } else if (isXULElement(targetNode)) {
1263 x = targetNode.getBoundingClientRect().width / 2;
1264 y = targetNode.getBoundingClientRect().height / 2;
1268 synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {});
1271 this.finalCheck = function synthClick_finalCheck() {
1272 // Scroll top window back.
1273 window.top.scrollTo(0, 0);
1276 this.getID = function synthClick_getID() {
1277 return prettyName(aNodeOrID) + " click";
1282 * Scrolls the node into view.
1284 function scrollIntoView(aNodeOrID, aCheckerOrEventSeq) {
1285 this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
1287 this.invoke = function scrollIntoView_invoke() {
1288 var targetNode = this.DOMNode;
1289 if (isHTMLElement(targetNode)) {
1290 targetNode.scrollIntoView(true);
1291 } else if (isXULElement(targetNode)) {
1292 var targetAcc = getAccessible(targetNode);
1293 targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
1297 this.getID = function scrollIntoView_getID() {
1298 return prettyName(aNodeOrID) + " scrollIntoView";
1303 * Mouse move invoker.
1305 function synthMouseMove(aID, aCheckerOrEventSeq) {
1306 this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
1308 this.invoke = function synthMouseMove_invoke() {
1309 synthesizeMouse(this.DOMNode, 5, 5, { type: "mousemove" });
1310 synthesizeMouse(this.DOMNode, 6, 6, { type: "mousemove" });
1313 this.getID = function synthMouseMove_getID() {
1314 return prettyName(aID) + " mouse move";
1319 * General key press invoker.
1321 function synthKey(aNodeOrID, aKey, aArgs, aCheckerOrEventSeq) {
1322 this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
1324 this.invoke = function synthKey_invoke() {
1325 synthesizeKey(this.mKey, this.mArgs, this.mWindow);
1328 this.getID = function synthKey_getID() {
1329 var key = this.mKey;
1330 switch (this.mKey) {
1360 if (aArgs.shiftKey) {
1363 if (aArgs.ctrlKey) {
1370 return prettyName(aNodeOrID) + " '" + key + " ' key";
1374 this.mArgs = aArgs ? aArgs : {};
1375 this.mWindow = aArgs ? aArgs.window : null;
1381 function synthTab(aNodeOrID, aCheckerOrEventSeq, aWindow) {
1382 this.__proto__ = new synthKey(
1385 { shiftKey: false, window: aWindow },
1391 * Shift tab key invoker.
1393 function synthShiftTab(aNodeOrID, aCheckerOrEventSeq) {
1394 this.__proto__ = new synthKey(
1403 * Escape key invoker.
1405 function synthEscapeKey(aNodeOrID, aCheckerOrEventSeq) {
1406 this.__proto__ = new synthKey(
1415 * Down arrow key invoker.
1417 function synthDownKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
1418 this.__proto__ = new synthKey(
1427 * Up arrow key invoker.
1429 function synthUpKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
1430 this.__proto__ = new synthKey(aNodeOrID, "VK_UP", aArgs, aCheckerOrEventSeq);
1434 * Left arrow key invoker.
1436 function synthLeftKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
1437 this.__proto__ = new synthKey(
1446 * Right arrow key invoker.
1448 function synthRightKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
1449 this.__proto__ = new synthKey(
1460 function synthHomeKey(aNodeOrID, aCheckerOrEventSeq) {
1461 this.__proto__ = new synthKey(aNodeOrID, "VK_HOME", null, aCheckerOrEventSeq);
1467 function synthEndKey(aNodeOrID, aCheckerOrEventSeq) {
1468 this.__proto__ = new synthKey(aNodeOrID, "VK_END", null, aCheckerOrEventSeq);
1474 function synthEnterKey(aID, aCheckerOrEventSeq) {
1475 this.__proto__ = new synthKey(aID, "VK_RETURN", null, aCheckerOrEventSeq);
1479 * Synth alt + down arrow to open combobox.
1481 function synthOpenComboboxKey(aID, aCheckerOrEventSeq) {
1482 this.__proto__ = new synthDownKey(aID, aCheckerOrEventSeq, { altKey: true });
1484 this.getID = function synthOpenComboboxKey_getID() {
1485 return "open combobox (alt + down arrow) " + prettyName(aID);
1492 function synthFocus(aNodeOrID, aCheckerOrEventSeq) {
1493 var checkerOfEventSeq = aCheckerOrEventSeq
1494 ? aCheckerOrEventSeq
1495 : new focusChecker(aNodeOrID);
1496 this.__proto__ = new synthAction(aNodeOrID, checkerOfEventSeq);
1498 this.invoke = function synthFocus_invoke() {
1499 if (this.DOMNode.editor) {
1500 this.DOMNode.selectionStart = this.DOMNode.selectionEnd =
1501 this.DOMNode.value.length;
1503 this.DOMNode.focus();
1506 this.getID = function synthFocus_getID() {
1507 return prettyName(aNodeOrID) + " focus";
1512 * Focus invoker. Focus the HTML body of content document of iframe.
1514 function synthFocusOnFrame(aNodeOrID, aCheckerOrEventSeq) {
1515 var frameDoc = getNode(aNodeOrID).contentDocument;
1516 var checkerOrEventSeq = aCheckerOrEventSeq
1517 ? aCheckerOrEventSeq
1518 : new focusChecker(frameDoc);
1519 this.__proto__ = new synthAction(frameDoc, checkerOrEventSeq);
1521 this.invoke = function synthFocus_invoke() {
1522 this.DOMNode.body.focus();
1525 this.getID = function synthFocus_getID() {
1526 return prettyName(aNodeOrID) + " frame document focus";
1531 * Change the current item when the widget doesn't have a focus.
1533 function changeCurrentItem(aID, aItemID) {
1534 this.eventSeq = [new nofocusChecker()];
1536 this.invoke = function changeCurrentItem_invoke() {
1537 var controlNode = getNode(aID);
1538 var itemNode = getNode(aItemID);
1541 if (controlNode.localName == "input") {
1542 if (controlNode.checked) {
1546 controlNode.checked = true;
1550 if (controlNode.localName == "select") {
1551 if (controlNode.selectedIndex == itemNode.index) {
1555 controlNode.selectedIndex = itemNode.index;
1560 if (controlNode.localName == "tree") {
1561 if (controlNode.currentIndex == aItemID) {
1565 controlNode.currentIndex = aItemID;
1569 if (controlNode.localName == "menulist") {
1570 if (controlNode.selectedItem == itemNode) {
1574 controlNode.selectedItem = itemNode;
1578 if (controlNode.currentItem == itemNode) {
1581 "Error in test: proposed current item is already current" +
1586 controlNode.currentItem = itemNode;
1589 this.getID = function changeCurrentItem_getID() {
1590 return "current item change for " + prettyName(aID);
1593 this.reportError = function changeCurrentItem_reportError() {
1596 "Error in test: proposed current item '" +
1598 "' is already current"
1604 * Toggle top menu invoker.
1606 function toggleTopMenu(aID, aCheckerOrEventSeq) {
1607 this.__proto__ = new synthKey(aID, "VK_ALT", null, aCheckerOrEventSeq);
1609 this.getID = function toggleTopMenu_getID() {
1610 return "toggle top menu on " + prettyName(aID);
1615 * Context menu invoker.
1617 function synthContextMenu(aID, aCheckerOrEventSeq) {
1618 this.__proto__ = new synthClick(aID, aCheckerOrEventSeq, {
1620 type: "contextmenu",
1623 this.getID = function synthContextMenu_getID() {
1624 return "context menu on " + prettyName(aID);
1629 * Open combobox, autocomplete and etc popup, check expandable states.
1631 function openCombobox(aComboboxID) {
1633 new stateChangeChecker(STATE_EXPANDED, false, true, aComboboxID),
1636 this.invoke = function openCombobox_invoke() {
1637 getNode(aComboboxID).focus();
1638 synthesizeKey("VK_DOWN", { altKey: true });
1641 this.getID = function openCombobox_getID() {
1642 return "open combobox " + prettyName(aComboboxID);
1647 * Close combobox, autocomplete and etc popup, check expandable states.
1649 function closeCombobox(aComboboxID) {
1651 new stateChangeChecker(STATE_EXPANDED, false, false, aComboboxID),
1654 this.invoke = function closeCombobox_invoke() {
1655 synthesizeKey("KEY_Escape");
1658 this.getID = function closeCombobox_getID() {
1659 return "close combobox " + prettyName(aComboboxID);
1664 * Select all invoker.
1666 function synthSelectAll(aNodeOrID, aCheckerOrEventSeq) {
1667 this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
1669 this.invoke = function synthSelectAll_invoke() {
1670 if (ChromeUtils.getClassName(this.DOMNode) === "HTMLInputElement") {
1671 this.DOMNode.select();
1673 window.getSelection().selectAllChildren(this.DOMNode);
1677 this.getID = function synthSelectAll_getID() {
1678 return aNodeOrID + " selectall";
1683 * Move the caret to the end of line.
1685 function moveToLineEnd(aID, aCaretOffset) {
1687 this.__proto__ = new synthKey(
1691 new caretMoveChecker(aCaretOffset, true, aID)
1694 this.__proto__ = new synthEndKey(
1696 new caretMoveChecker(aCaretOffset, true, aID)
1700 this.getID = function moveToLineEnd_getID() {
1701 return "move to line end in " + prettyName(aID);
1706 * Move the caret to the end of previous line if any.
1708 function moveToPrevLineEnd(aID, aCaretOffset) {
1709 this.__proto__ = new synthAction(
1711 new caretMoveChecker(aCaretOffset, true, aID)
1714 this.invoke = function moveToPrevLineEnd_invoke() {
1715 synthesizeKey("KEY_ArrowUp");
1718 synthesizeKey("Key_ArrowRight", { metaKey: true });
1720 synthesizeKey("KEY_End");
1724 this.getID = function moveToPrevLineEnd_getID() {
1725 return "move to previous line end in " + prettyName(aID);
1730 * Move the caret to begining of the line.
1732 function moveToLineStart(aID, aCaretOffset) {
1734 this.__proto__ = new synthKey(
1738 new caretMoveChecker(aCaretOffset, true, aID)
1741 this.__proto__ = new synthHomeKey(
1743 new caretMoveChecker(aCaretOffset, true, aID)
1747 this.getID = function moveToLineEnd_getID() {
1748 return "move to line start in " + prettyName(aID);
1753 * Move the caret to begining of the text.
1755 function moveToTextStart(aID) {
1757 this.__proto__ = new synthKey(
1761 new caretMoveChecker(0, true, aID)
1764 this.__proto__ = new synthKey(
1768 new caretMoveChecker(0, true, aID)
1772 this.getID = function moveToTextStart_getID() {
1773 return "move to text start in " + prettyName(aID);
1778 * Move the caret in text accessible.
1780 function moveCaretToDOMPoint(
1788 this.target = getAccessible(aID, [nsIAccessibleText]);
1789 this.DOMPointNode = getNode(aDOMPointNodeID);
1790 this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
1791 this.focusNode = this.focus ? this.focus.DOMNode : null;
1793 this.invoke = function moveCaretToDOMPoint_invoke() {
1794 if (this.focusNode) {
1795 this.focusNode.focus();
1798 var selection = this.DOMPointNode.ownerGlobal.getSelection();
1799 var selRange = selection.getRangeAt(0);
1800 selRange.setStart(this.DOMPointNode, aDOMPointOffset);
1801 selRange.collapse(true);
1803 selection.removeRange(selRange);
1804 selection.addRange(selRange);
1807 this.getID = function moveCaretToDOMPoint_getID() {
1812 prettyName(aDOMPointNodeID) +
1813 " node with offset " +
1818 this.finalCheck = function moveCaretToDOMPoint_finalCheck() {
1824 this.eventSeq = [new caretMoveChecker(aExpectedOffset, true, this.target)];
1827 this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
1832 * Set caret offset in text accessible.
1834 function setCaretOffset(aID, aOffset, aFocusTargetID) {
1835 this.target = getAccessible(aID, [nsIAccessibleText]);
1836 this.offset = aOffset == -1 ? this.target.characterCount : aOffset;
1837 this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
1839 this.invoke = function setCaretOffset_invoke() {
1840 this.target.caretOffset = this.offset;
1843 this.getID = function setCaretOffset_getID() {
1844 return "Set caretOffset on " + prettyName(aID) + " at " + this.offset;
1847 this.eventSeq = [new caretMoveChecker(this.offset, true, this.target)];
1850 this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
1854 // //////////////////////////////////////////////////////////////////////////////
1855 // Event queue checkers
1858 * Common invoker checker (see eventSeq of eventQueue).
1860 function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync) {
1861 this.type = aEventType;
1862 this.async = aIsAsync;
1864 this.__defineGetter__("target", invokerChecker_targetGetter);
1865 this.__defineSetter__("target", invokerChecker_targetSetter);
1867 // implementation details
1868 function invokerChecker_targetGetter() {
1869 if (typeof this.mTarget == "function") {
1870 return this.mTarget.call(null, this.mTargetFuncArg);
1872 if (typeof this.mTarget == "string") {
1873 return getNode(this.mTarget);
1876 return this.mTarget;
1879 function invokerChecker_targetSetter(aValue) {
1880 this.mTarget = aValue;
1881 return this.mTarget;
1884 this.__defineGetter__("targetDescr", invokerChecker_targetDescrGetter);
1886 function invokerChecker_targetDescrGetter() {
1887 if (typeof this.mTarget == "function") {
1888 return this.mTarget.name + ", arg: " + this.mTargetFuncArg;
1891 return prettyName(this.mTarget);
1894 this.mTarget = aTargetOrFunc;
1895 this.mTargetFuncArg = aTargetFuncArg;
1899 * event checker that forces preceeding async events to happen before this
1902 function orderChecker() {
1903 // XXX it doesn't actually work to inherit from invokerChecker, but maybe we
1905 // this.__proto__ = new invokerChecker(null, null, null, false);
1909 * Generic invoker checker for todo events.
1911 function todo_invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) {
1912 this.__proto__ = new invokerChecker(
1922 * Generic invoker checker for unexpected events.
1924 function unexpectedInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) {
1925 this.__proto__ = new invokerChecker(
1932 this.unexpected = true;
1936 * Common invoker checker for async events.
1938 function asyncInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) {
1939 this.__proto__ = new invokerChecker(
1947 function focusChecker(aTargetOrFunc, aTargetFuncArg) {
1948 this.__proto__ = new invokerChecker(
1955 this.unique = true; // focus event must be unique for invoker action
1957 this.check = function focusChecker_check(aEvent) {
1958 testStates(aEvent.accessible, STATE_FOCUSED);
1962 function nofocusChecker(aID) {
1963 this.__proto__ = new focusChecker(aID);
1964 this.unexpected = true;
1968 * Text inserted/removed events checker.
1969 * @param aFromUser [in, optional] kNotFromUserInput or kFromUserInput
1971 function textChangeChecker(
1980 this.target = getNode(aID);
1981 this.type = aIsInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
1982 this.startOffset = aStart;
1983 this.endOffset = aEnd;
1984 this.textOrFunc = aTextOrFunc;
1985 this.async = aAsync;
1987 this.match = function stextChangeChecker_match(aEvent) {
1989 !(aEvent instanceof nsIAccessibleTextChangeEvent) ||
1990 aEvent.accessible !== getAccessible(this.target)
1995 let tcEvent = aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
1997 typeof this.textOrFunc === "function"
2000 return modifiedText === tcEvent.modifiedText;
2003 this.check = function textChangeChecker_check(aEvent) {
2004 aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
2007 typeof this.textOrFunc == "function"
2010 var modifiedTextLen =
2011 this.endOffset == -1 ? modifiedText.length : aEnd - aStart;
2016 "Wrong start offset for " + prettyName(aID)
2018 is(aEvent.length, modifiedTextLen, "Wrong length for " + prettyName(aID));
2019 var changeInfo = aIsInserted ? "inserted" : "removed";
2023 "Text was " + changeInfo + " for " + prettyName(aID)
2026 aEvent.modifiedText,
2028 "Wrong " + changeInfo + " text for " + prettyName(aID)
2030 if (typeof aFromUser != "undefined") {
2032 aEvent.isFromUserInput,
2034 "wrong value of isFromUserInput() for " + prettyName(aID)
2041 * Caret move events checker.
2043 function caretMoveChecker(
2045 aIsSelectionCollapsed,
2050 this.__proto__ = new invokerChecker(
2051 EVENT_TEXT_CARET_MOVED,
2057 this.check = function caretMoveChecker_check(aEvent) {
2058 let evt = aEvent.QueryInterface(nsIAccessibleCaretMoveEvent);
2062 "Wrong caret offset for " + prettyName(aEvent.accessible)
2065 evt.isSelectionCollapsed,
2066 aIsSelectionCollapsed,
2067 "wrong collapsed value for " + prettyName(aEvent.accessible)
2072 function asyncCaretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg) {
2073 this.__proto__ = new caretMoveChecker(
2075 true, // Caret is collapsed
2083 * Text selection change checker.
2085 function textSelectionChecker(
2089 aRangeStartContainer,
2094 this.__proto__ = new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID);
2096 this.check = function textSelectionChecker_check(aEvent) {
2097 if (aStartOffset == aEndOffset) {
2098 ok(true, "Collapsed selection triggered text selection change event.");
2100 testTextGetSelection(aID, aStartOffset, aEndOffset, 0);
2102 // Test selection test range
2103 let selectionRanges = aEvent.QueryInterface(
2104 nsIAccessibleTextSelectionChangeEvent
2106 let range = selectionRanges.queryElementAt(0, nsIAccessibleTextRange);
2108 range.startContainer,
2109 getAccessible(aRangeStartContainer),
2110 "correct range start container"
2112 is(range.startOffset, aRangeStartOffset, "correct range start offset");
2113 is(range.endOffset, aRangeEndOffset, "correct range end offset");
2116 getAccessible(aRangeEndContainer),
2117 "correct range end container"
2124 * Object attribute changed checker
2126 function objAttrChangedChecker(aID, aAttr) {
2127 this.__proto__ = new invokerChecker(EVENT_OBJECT_ATTRIBUTE_CHANGED, aID);
2129 this.check = function objAttrChangedChecker_check(aEvent) {
2132 var event = aEvent.QueryInterface(
2133 nsIAccessibleObjectAttributeChangedEvent
2136 ok(false, "Object attribute changed event was expected");
2144 event.changedAttribute,
2146 "Wrong attribute name of the object attribute changed event."
2150 this.match = function objAttrChangedChecker_match(aEvent) {
2151 if (aEvent instanceof nsIAccessibleObjectAttributeChangedEvent) {
2152 var scEvent = aEvent.QueryInterface(
2153 nsIAccessibleObjectAttributeChangedEvent
2156 aEvent.accessible == getAccessible(this.target) &&
2157 scEvent.changedAttribute == aAttr
2165 * State change checker.
2167 function stateChangeChecker(
2174 aSkipCurrentStateCheck
2176 this.__proto__ = new invokerChecker(
2183 this.check = function stateChangeChecker_check(aEvent) {
2186 var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
2188 ok(false, "State change event was expected");
2198 "Wrong extra state bit of the statechange event."
2204 "Wrong state of the statechange event."
2206 is(event.isEnabled, aIsEnabled, "Wrong state of statechange event state");
2208 if (aSkipCurrentStateCheck) {
2209 todo(false, "State checking was skipped!");
2213 var state = aIsEnabled ? (aIsExtraState ? 0 : aState) : 0;
2214 var extraState = aIsEnabled ? (aIsExtraState ? aState : 0) : 0;
2215 var unxpdState = aIsEnabled ? 0 : aIsExtraState ? 0 : aState;
2216 var unxpdExtraState = aIsEnabled ? 0 : aIsExtraState ? aState : 0;
2226 this.match = function stateChangeChecker_match(aEvent) {
2227 if (aEvent instanceof nsIAccessibleStateChangeEvent) {
2228 var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
2230 aEvent.accessible == getAccessible(this.target) &&
2231 scEvent.state == aState
2238 function asyncStateChangeChecker(
2245 this.__proto__ = new stateChangeChecker(
2256 * Expanded state change checker.
2258 function expandedStateChecker(aIsEnabled, aTargetOrFunc, aTargetFuncArg) {
2259 this.__proto__ = new invokerChecker(
2265 this.check = function expandedStateChecker_check(aEvent) {
2268 var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
2270 ok(false, "State change event was expected");
2277 is(event.state, STATE_EXPANDED, "Wrong state of the statechange event.");
2281 "Wrong extra state bit of the statechange event."
2283 is(event.isEnabled, aIsEnabled, "Wrong state of statechange event state");
2285 testStates(event.accessible, aIsEnabled ? STATE_EXPANDED : STATE_COLLAPSED);
2289 // //////////////////////////////////////////////////////////////////////////////
2290 // Event sequances (array of predefined checkers)
2293 * Event seq for single selection change.
2295 function selChangeSeq(aUnselectedID, aSelectedID) {
2296 if (!aUnselectedID) {
2298 new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
2299 new invokerChecker(EVENT_SELECTION, aSelectedID),
2303 // Return two possible scenarios: depending on widget type when selection is
2304 // moved the the order of items that get selected and unselected may vary.
2307 new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
2308 new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
2309 new invokerChecker(EVENT_SELECTION, aSelectedID),
2312 new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
2313 new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
2314 new invokerChecker(EVENT_SELECTION, aSelectedID),
2320 * Event seq for item removed form the selection.
2322 function selRemoveSeq(aUnselectedID) {
2324 new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
2325 new invokerChecker(EVENT_SELECTION_REMOVE, aUnselectedID),
2330 * Event seq for item added to the selection.
2332 function selAddSeq(aSelectedID) {
2334 new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
2335 new invokerChecker(EVENT_SELECTION_ADD, aSelectedID),
2339 // //////////////////////////////////////////////////////////////////////////////
2340 // Private implementation details.
2341 // //////////////////////////////////////////////////////////////////////////////
2343 // //////////////////////////////////////////////////////////////////////////////
2346 var gA11yEventListeners = {};
2347 var gA11yEventApplicantsCount = 0;
2349 var gA11yEventObserver = {
2350 // eslint-disable-next-line complexity
2351 observe: function observe(aSubject, aTopic, aData) {
2352 if (aTopic != "accessible-event") {
2358 event = aSubject.QueryInterface(nsIAccessibleEvent);
2360 // After a test is aborted (i.e. timed out by the harness), this exception is soon triggered.
2361 // Remove the leftover observer, otherwise it "leaks" to all the following tests.
2362 Services.obs.removeObserver(this, "accessible-event");
2363 // Forward the exception, with added explanation.
2365 "[accessible/events.js, gA11yEventObserver.observe] This is expected " +
2366 `if a previous test has been aborted... Initial exception was: [ ${ex} ]`
2369 var listenersArray = gA11yEventListeners[event.eventType];
2371 var eventFromDumpArea = false;
2372 if (gLogger.isEnabled()) {
2374 eventFromDumpArea = true;
2376 var target = event.DOMNode;
2377 var dumpElm = gA11yEventDumpID
2378 ? document.getElementById(gA11yEventDumpID)
2382 var parent = target;
2383 while (parent && parent != dumpElm) {
2384 parent = parent.parentNode;
2388 if (!dumpElm || parent != dumpElm) {
2389 var type = eventTypeToString(event.eventType);
2390 var info = "Event type: " + type;
2392 if (event instanceof nsIAccessibleStateChangeEvent) {
2393 var stateStr = statesToString(
2394 event.isExtraState ? 0 : event.state,
2395 event.isExtraState ? event.state : 0
2397 info += ", state: " + stateStr + ", is enabled: " + event.isEnabled;
2398 } else if (event instanceof nsIAccessibleTextChangeEvent) {
2405 (event.isInserted ? "inserted" : "removed") +
2410 info += ". Target: " + prettyName(event.accessible);
2412 if (listenersArray) {
2413 info += ". Listeners count: " + listenersArray.length;
2416 if (gLogger.hasFeature("parentchain:" + type)) {
2417 info += "\nParent chain:\n";
2418 var acc = event.accessible;
2420 info += " " + prettyName(acc) + "\n";
2425 eventFromDumpArea = false;
2430 // Do not notify listeners if event is result of event log changes.
2431 if (!listenersArray || eventFromDumpArea) {
2435 for (var index = 0; index < listenersArray.length; index++) {
2436 listenersArray[index].handleEvent(event);
2441 function listenA11yEvents(aStartToListen) {
2442 if (aStartToListen) {
2443 // Add observer when adding the first applicant only.
2444 if (!gA11yEventApplicantsCount++) {
2445 Services.obs.addObserver(gA11yEventObserver, "accessible-event");
2448 // Remove observer when there are no more applicants only.
2449 // '< 0' case should not happen, but just in case: removeObserver() will throw.
2450 // eslint-disable-next-line no-lonely-if
2451 if (--gA11yEventApplicantsCount <= 0) {
2452 Services.obs.removeObserver(gA11yEventObserver, "accessible-event");
2457 function addA11yEventListener(aEventType, aEventHandler) {
2458 if (!(aEventType in gA11yEventListeners)) {
2459 gA11yEventListeners[aEventType] = [];
2462 var listenersArray = gA11yEventListeners[aEventType];
2463 var index = listenersArray.indexOf(aEventHandler);
2465 listenersArray.push(aEventHandler);
2469 function removeA11yEventListener(aEventType, aEventHandler) {
2470 var listenersArray = gA11yEventListeners[aEventType];
2471 if (!listenersArray) {
2475 var index = listenersArray.indexOf(aEventHandler);
2480 listenersArray.splice(index, 1);
2482 if (!listenersArray.length) {
2483 gA11yEventListeners[aEventType] = null;
2484 delete gA11yEventListeners[aEventType];
2491 * Used to dump debug information.
2495 * Return true if dump is enabled.
2497 isEnabled: function debugOutput_isEnabled() {
2499 gA11yEventDumpID || gA11yEventDumpToConsole || gA11yEventDumpToAppConsole
2504 * Dump information into DOM and console if applicable.
2506 log: function logger_log(aMsg) {
2507 this.logToConsole(aMsg);
2508 this.logToAppConsole(aMsg);
2509 this.logToDOM(aMsg);
2513 * Log message to DOM.
2515 * @param aMsg [in] the primary message
2516 * @param aHasIndent [in, optional] if specified the message has an indent
2517 * @param aPreEmphText [in, optional] the text is colored and appended prior
2520 logToDOM: function logger_logToDOM(aMsg, aHasIndent, aPreEmphText) {
2521 if (gA11yEventDumpID == "") {
2525 var dumpElm = document.getElementById(gA11yEventDumpID);
2529 "No dump element '" + gA11yEventDumpID + "' within the document!"
2534 var containerTagName =
2535 ChromeUtils.getClassName(document) == "HTMLDocument"
2539 var container = document.createElement(containerTagName);
2541 container.setAttribute("style", "padding-left: 10px;");
2546 ChromeUtils.getClassName(document) == "HTMLDocument"
2549 var emphElm = document.createElement(inlineTagName);
2550 emphElm.setAttribute("style", "color: blue;");
2551 emphElm.textContent = aPreEmphText;
2553 container.appendChild(emphElm);
2556 var textNode = document.createTextNode(aMsg);
2557 container.appendChild(textNode);
2559 dumpElm.appendChild(container);
2563 * Log message to console.
2565 logToConsole: function logger_logToConsole(aMsg) {
2566 if (gA11yEventDumpToConsole) {
2567 dump("\n" + aMsg + "\n");
2572 * Log message to error console.
2574 logToAppConsole: function logger_logToAppConsole(aMsg) {
2575 if (gA11yEventDumpToAppConsole) {
2576 Services.console.logStringMessage("events: " + aMsg);
2581 * Return true if logging feature is enabled.
2583 hasFeature: function logger_hasFeature(aFeature) {
2584 var startIdx = gA11yEventDumpFeature.indexOf(aFeature);
2585 if (startIdx == -1) {
2589 var endIdx = startIdx + aFeature.length;
2591 endIdx == gA11yEventDumpFeature.length ||
2592 gA11yEventDumpFeature[endIdx] == ";"
2597 // //////////////////////////////////////////////////////////////////////////////
2601 * Base class of sequence item.
2603 function sequenceItem(aProcessor, aEventType, aTarget, aItemID) {
2606 this.startProcess = function sequenceItem_startProcess() {
2607 this.queue.invoke();
2610 this.queue = new eventQueue();
2611 this.queue.onFinish = function () {
2612 aProcessor.onProcessed();
2613 return DO_NOT_FINISH_TEST;
2617 invoke: function invoker_invoke() {
2618 return aProcessor.process();
2620 getID: function invoker_getID() {
2623 eventSeq: [new invokerChecker(aEventType, aTarget)],
2626 this.queue.push(invoker);
2629 // //////////////////////////////////////////////////////////////////////////////
2630 // Event queue invokers
2633 * Invoker base class for prepare an action.
2635 function synthAction(aNodeOrID, aEventsObj) {
2636 this.DOMNode = getNode(aNodeOrID);
2639 var scenarios = null;
2640 if (aEventsObj instanceof Array) {
2641 if (aEventsObj[0] instanceof Array) {
2642 scenarios = aEventsObj;
2646 scenarios = [aEventsObj];
2649 scenarios = [[aEventsObj]]; // a single checker object
2652 for (var i = 0; i < scenarios.length; i++) {
2653 defineScenario(this, scenarios[i]);
2657 this.getID = function synthAction_getID() {
2658 return prettyName(aNodeOrID) + " action";