accessible/tests/mochitest/events.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

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)
  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;
  1022   if (aUnexpectedEventSeq) {
  1023     for (var idx = 0; idx < aUnexpectedEventSeq.length; idx++) {
  1024       aUnexpectedEventSeq[idx].unexpected = true;
  1025       aUnexpectedEventSeq[idx].async = false;
  1028     aEventSeq = aEventSeq.concat(aUnexpectedEventSeq);
  1031   aInvoker.scenarios.push(aEventSeq);
  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.
  1040  * Note, checker object or array of checker objects is optional.
  1041  */
  1043 /**
  1044  * Click invoker.
  1045  */
  1046 function synthClick(aNodeOrID, aCheckerOrEventSeq, aArgs)
  1048   this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
  1050   this.invoke = function synthClick_invoke()
  1052     var targetNode = this.DOMNode;
  1053     if (targetNode instanceof nsIDOMDocument) {
  1054       targetNode =
  1055         this.DOMNode.body ? this.DOMNode.body : this.DOMNode.documentElement;
  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);
  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;
  1073     synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {});
  1076   this.finalCheck = function synthClick_finalCheck()
  1078     // Scroll top window back.
  1079     window.top.scrollTo(0, 0);
  1082   this.getID = function synthClick_getID()
  1084     return prettyName(aNodeOrID) + " click";
  1088 /**
  1089  * Mouse move invoker.
  1090  */
  1091 function synthMouseMove(aID, aCheckerOrEventSeq)
  1093   this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
  1095   this.invoke = function synthMouseMove_invoke()
  1097     synthesizeMouse(this.DOMNode, 1, 1, { type: "mousemove" });
  1098     synthesizeMouse(this.DOMNode, 2, 2, { type: "mousemove" });
  1101   this.getID = function synthMouseMove_getID()
  1103     return prettyName(aID) + " mouse move";
  1107 /**
  1108  * General key press invoker.
  1109  */
  1110 function synthKey(aNodeOrID, aKey, aArgs, aCheckerOrEventSeq)
  1112   this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
  1114   this.invoke = function synthKey_invoke()
  1116     synthesizeKey(this.mKey, this.mArgs, this.mWindow);
  1119   this.getID = function synthKey_getID()
  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;
  1151     if (aArgs) {
  1152       if (aArgs.shiftKey)
  1153         key += " shift";
  1154       if (aArgs.ctrlKey)
  1155         key += " ctrl";
  1156       if (aArgs.altKey)
  1157         key += " alt";
  1159     return prettyName(aNodeOrID) + " '" + key + " ' key";
  1162   this.mKey = aKey;
  1163   this.mArgs = aArgs ? aArgs : {};
  1164   this.mWindow = aArgs ? aArgs.window : null;
  1167 /**
  1168  * Tab key invoker.
  1169  */
  1170 function synthTab(aNodeOrID, aCheckerOrEventSeq, aWindow)
  1172   this.__proto__ = new synthKey(aNodeOrID, "VK_TAB",
  1173                                 { shiftKey: false, window: aWindow },
  1174                                 aCheckerOrEventSeq);
  1177 /**
  1178  * Shift tab key invoker.
  1179  */
  1180 function synthShiftTab(aNodeOrID, aCheckerOrEventSeq)
  1182   this.__proto__ = new synthKey(aNodeOrID, "VK_TAB", { shiftKey: true },
  1183                                 aCheckerOrEventSeq);
  1186 /**
  1187  * Escape key invoker.
  1188  */
  1189 function synthEscapeKey(aNodeOrID, aCheckerOrEventSeq)
  1191   this.__proto__ = new synthKey(aNodeOrID, "VK_ESCAPE", null,
  1192                                 aCheckerOrEventSeq);
  1195 /**
  1196  * Down arrow key invoker.
  1197  */
  1198 function synthDownKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
  1200   this.__proto__ = new synthKey(aNodeOrID, "VK_DOWN", aArgs,
  1201                                 aCheckerOrEventSeq);
  1204 /**
  1205  * Up arrow key invoker.
  1206  */
  1207 function synthUpKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
  1209   this.__proto__ = new synthKey(aNodeOrID, "VK_UP", aArgs,
  1210                                 aCheckerOrEventSeq);
  1213 /**
  1214  * Left arrow key invoker.
  1215  */
  1216 function synthLeftKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
  1218   this.__proto__ = new synthKey(aNodeOrID, "VK_LEFT", aArgs, aCheckerOrEventSeq);
  1221 /**
  1222  * Right arrow key invoker.
  1223  */
  1224 function synthRightKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
  1226   this.__proto__ = new synthKey(aNodeOrID, "VK_RIGHT", aArgs, aCheckerOrEventSeq);
  1229 /**
  1230  * Home key invoker.
  1231  */
  1232 function synthHomeKey(aNodeOrID, aCheckerOrEventSeq)
  1234   this.__proto__ = new synthKey(aNodeOrID, "VK_HOME", null, aCheckerOrEventSeq);
  1237 /**
  1238  * End key invoker.
  1239  */
  1240 function synthEndKey(aNodeOrID, aCheckerOrEventSeq)
  1242   this.__proto__ = new synthKey(aNodeOrID, "VK_END", null, aCheckerOrEventSeq);
  1245 /**
  1246  * Enter key invoker
  1247  */
  1248 function synthEnterKey(aID, aCheckerOrEventSeq)
  1250   this.__proto__ = new synthKey(aID, "VK_RETURN", null, aCheckerOrEventSeq);
  1253 /**
  1254  * Synth alt + down arrow to open combobox.
  1255  */
  1256 function synthOpenComboboxKey(aID, aCheckerOrEventSeq)
  1258   this.__proto__ = new synthDownKey(aID, aCheckerOrEventSeq, { altKey: true });
  1260   this.getID = function synthOpenComboboxKey_getID()
  1262     return "open combobox (atl + down arrow) " + prettyName(aID);
  1266 /**
  1267  * Focus invoker.
  1268  */
  1269 function synthFocus(aNodeOrID, aCheckerOrEventSeq)
  1271   var checkerOfEventSeq =
  1272     aCheckerOrEventSeq ? aCheckerOrEventSeq : new focusChecker(aNodeOrID);
  1273   this.__proto__ = new synthAction(aNodeOrID, checkerOfEventSeq);
  1275   this.invoke = function synthFocus_invoke()
  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;
  1282     this.DOMNode.focus();
  1285   this.getID = function synthFocus_getID() 
  1287     return prettyName(aNodeOrID) + " focus";
  1291 /**
  1292  * Focus invoker. Focus the HTML body of content document of iframe.
  1293  */
  1294 function synthFocusOnFrame(aNodeOrID, aCheckerOrEventSeq)
  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()
  1303     this.DOMNode.body.focus();
  1306   this.getID = function synthFocus_getID() 
  1308     return prettyName(aNodeOrID) + " frame document focus";
  1312 /**
  1313  * Change the current item when the widget doesn't have a focus.
  1314  */
  1315 function changeCurrentItem(aID, aItemID)
  1317   this.eventSeq = [ new nofocusChecker() ];
  1319   this.invoke = function changeCurrentItem_invoke()
  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;
  1333     if (controlNode.localName == "select") {
  1334       if (controlNode.selectedIndex == itemNode.index)
  1335         this.reportError();
  1337       controlNode.selectedIndex = itemNode.index;
  1338       return;
  1341     // XUL
  1342     if (controlNode.localName == "tree") {
  1343       if (controlNode.currentIndex == aItemID)
  1344         this.reportError();
  1346       controlNode.currentIndex = aItemID;
  1347       return;
  1350     if (controlNode.localName == "menulist") {
  1351       if (controlNode.selectedItem == itemNode)
  1352         this.reportError();
  1354       controlNode.selectedItem = itemNode;
  1355       return;
  1358     if (controlNode.currentItem == itemNode)
  1359       ok(false, "Error in test: proposed current item is already current" + prettyName(aID));
  1361     controlNode.currentItem = itemNode;
  1364   this.getID = function changeCurrentItem_getID()
  1366     return "current item change for " + prettyName(aID);
  1369   this.reportError = function changeCurrentItem_reportError()
  1371     ok(false,
  1372        "Error in test: proposed current item '" + aItemID + "' is already current");
  1376 /**
  1377  * Toggle top menu invoker.
  1378  */
  1379 function toggleTopMenu(aID, aCheckerOrEventSeq)
  1381   this.__proto__ = new synthKey(aID, "VK_ALT", null,
  1382                                 aCheckerOrEventSeq);
  1384   this.getID = function toggleTopMenu_getID()
  1386     return "toggle top menu on " + prettyName(aID);
  1390 /**
  1391  * Context menu invoker.
  1392  */
  1393 function synthContextMenu(aID, aCheckerOrEventSeq)
  1395   this.__proto__ = new synthClick(aID, aCheckerOrEventSeq,
  1396                                   { button: 0, type: "contextmenu" });
  1398   this.getID = function synthContextMenu_getID()
  1400     return "context menu on " + prettyName(aID);
  1404 /**
  1405  * Open combobox, autocomplete and etc popup, check expandable states.
  1406  */
  1407 function openCombobox(aComboboxID)
  1409   this.eventSeq = [
  1410     new stateChangeChecker(STATE_EXPANDED, false, true, aComboboxID)
  1411   ];
  1413   this.invoke = function openCombobox_invoke()
  1415     getNode(aComboboxID).focus();
  1416     synthesizeKey("VK_DOWN", { altKey: true });
  1419   this.getID = function openCombobox_getID()
  1421     return "open combobox " + prettyName(aComboboxID);
  1425 /**
  1426  * Close combobox, autocomplete and etc popup, check expandable states.
  1427  */
  1428 function closeCombobox(aComboboxID)
  1430   this.eventSeq = [
  1431     new stateChangeChecker(STATE_EXPANDED, false, false, aComboboxID)
  1432   ];
  1434   this.invoke = function closeCombobox_invoke()
  1436     synthesizeKey("VK_ESCAPE", { });
  1439   this.getID = function closeCombobox_getID()
  1441     return "close combobox " + prettyName(aComboboxID);
  1446 /**
  1447  * Select all invoker.
  1448  */
  1449 function synthSelectAll(aNodeOrID, aCheckerOrEventSeq)
  1451   this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
  1453   this.invoke = function synthSelectAll_invoke()
  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);
  1464   this.getID = function synthSelectAll_getID()
  1466     return aNodeOrID + " selectall";
  1470 /**
  1471  * Move the caret to the end of line.
  1472  */
  1473 function moveToLineEnd(aID, aCaretOffset)
  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));
  1483   this.getID = function moveToLineEnd_getID()
  1485     return "move to line end in " + prettyName(aID);
  1489 /**
  1490  * Move the caret to the end of previous line if any.
  1491  */
  1492 function moveToPrevLineEnd(aID, aCaretOffset)
  1494   this.__proto__ = new synthAction(aID, new caretMoveChecker(aCaretOffset, aID));
  1496   this.invoke = function moveToPrevLineEnd_invoke()
  1498     synthesizeKey("VK_UP", { });
  1500     if (MAC)
  1501       synthesizeKey("VK_RIGHT", { metaKey: true });
  1502     else
  1503       synthesizeKey("VK_END", { });
  1506   this.getID = function moveToPrevLineEnd_getID()
  1508     return "move to previous line end in " + prettyName(aID);
  1512 /**
  1513  * Move the caret to begining of the line.
  1514  */
  1515 function moveToLineStart(aID, aCaretOffset)
  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));
  1525   this.getID = function moveToLineEnd_getID()
  1527     return "move to line start in " + prettyName(aID);
  1531 /**
  1532  * Move the caret to begining of the text.
  1533  */
  1534 function moveToTextStart(aID)
  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));
  1544   this.getID = function moveToTextStart_getID()
  1546     return "move to text start in " + prettyName(aID);
  1550 /**
  1551  * Move the caret in text accessible.
  1552  */
  1553 function moveCaretToDOMPoint(aID, aDOMPointNodeID, aDOMPointOffset,
  1554                              aExpectedOffset, aFocusTargetID,
  1555                              aCheckFunc)
  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()
  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);
  1576   this.getID = function moveCaretToDOMPoint_getID()
  1578    return "Set caret on " + prettyName(aID) + " at point: " +
  1579      prettyName(aDOMPointNodeID) + " node with offset " + aDOMPointOffset;
  1582   this.finalCheck = function moveCaretToDOMPoint_finalCheck()
  1584     if (aCheckFunc)
  1585       aCheckFunc.call();
  1588   this.eventSeq = [
  1589     new caretMoveChecker(aExpectedOffset, this.target)
  1590   ];
  1592   if (this.focus)
  1593     this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
  1596 /**
  1597  * Set caret offset in text accessible.
  1598  */
  1599 function setCaretOffset(aID, aOffset, aFocusTargetID)
  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()
  1607     this.target.caretOffset = this.offset;
  1610   this.getID = function setCaretOffset_getID()
  1612    return "Set caretOffset on " + prettyName(aID) + " at " + this.offset;
  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));
  1624 ////////////////////////////////////////////////////////////////////////////////
  1625 // Event queue checkers
  1627 /**
  1628  * Common invoker checker (see eventSeq of eventQueue).
  1629  */
  1630 function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync)
  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()
  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;
  1649   function invokerChecker_targetSetter(aValue)
  1651     this.mTarget = aValue;
  1652     return this.mTarget;
  1655   this.__defineGetter__("targetDescr", invokerChecker_targetDescrGetter);
  1657   function invokerChecker_targetDescrGetter()
  1659     if (typeof this.mTarget == "function")
  1660       return this.mTarget.name + ", arg: " + this.mTargetFuncArg;
  1662     return prettyName(this.mTarget);
  1665   this.mTarget = aTargetOrFunc;
  1666   this.mTargetFuncArg = aTargetFuncArg;
  1669 /**
  1670  * Generic invoker checker for unexpected events.
  1671  */
  1672 function unexpectedInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
  1674   this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
  1675                                       aTargetFuncArg, true);
  1677   this.unexpected = true;
  1680 /**
  1681  * Common invoker checker for async events.
  1682  */
  1683 function asyncInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
  1685   this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
  1686                                       aTargetFuncArg, true);
  1689 function focusChecker(aTargetOrFunc, aTargetFuncArg)
  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)
  1698     testStates(aEvent.accessible, STATE_FOCUSED);
  1702 function nofocusChecker(aID)
  1704   this.__proto__ = new focusChecker(aID);
  1705   this.unexpected = true;
  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)
  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)
  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));
  1743 /**
  1744  * Caret move events checker.
  1745  */
  1746 function caretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg)
  1748   this.__proto__ = new invokerChecker(EVENT_TEXT_CARET_MOVED,
  1749                                       aTargetOrFunc, aTargetFuncArg);
  1751   this.check = function caretMoveChecker_check(aEvent)
  1753     is(aEvent.QueryInterface(nsIAccessibleCaretMoveEvent).caretOffset,
  1754        aCaretOffset,
  1755        "Wrong caret offset for " + prettyName(aEvent.accessible));
  1759 /**
  1760  * Text selection change checker.
  1761  */
  1762 function textSelectionChecker(aID, aStartOffset, aEndOffset)
  1764   this.__proto__ = new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID);
  1766   this.check = function textSelectionChecker_check(aEvent)
  1768     if (aStartOffset == aEndOffset) {
  1769       is(getAccessible(aID, [nsIAccessibleText]).caretOffset, aStartOffset,
  1770          "Wrong collapsed selection!");
  1771     } else {
  1772       testTextGetSelection(aID, aStartOffset, aEndOffset, 0);
  1777 /**
  1778  * State change checker.
  1779  */
  1780 function stateChangeChecker(aState, aIsExtraState, aIsEnabled,
  1781                             aTargetOrFunc, aTargetFuncArg, aIsAsync,
  1782                             aSkipCurrentStateCheck)
  1784   this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
  1785                                       aTargetFuncArg, aIsAsync);
  1787   this.check = function stateChangeChecker_check(aEvent)
  1789     var event = null;
  1790     try {
  1791       var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
  1792     } catch (e) {
  1793       ok(false, "State change event was expected");
  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;
  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);
  1818   this.match = function stateChangeChecker_match(aEvent)
  1820     if (aEvent instanceof nsIAccessibleStateChangeEvent) {
  1821       var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
  1822       return (aEvent.accessible == getAccessible(this.target)) &&
  1823         (scEvent.state == aState);
  1825     return false;
  1829 function asyncStateChangeChecker(aState, aIsExtraState, aIsEnabled,
  1830                                  aTargetOrFunc, aTargetFuncArg)
  1832   this.__proto__ = new stateChangeChecker(aState, aIsExtraState, aIsEnabled,
  1833                                           aTargetOrFunc, aTargetFuncArg, true);
  1836 /**
  1837  * Expanded state change checker.
  1838  */
  1839 function expandedStateChecker(aIsEnabled, aTargetOrFunc, aTargetFuncArg)
  1841   this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
  1842                                       aTargetFuncArg);
  1844   this.check = function expandedStateChecker_check(aEvent)
  1846     var event = null;
  1847     try {
  1848       var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
  1849     } catch (e) {
  1850       ok(false, "State change event was expected");
  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));
  1867 ////////////////////////////////////////////////////////////////////////////////
  1868 // Event sequances (array of predefined checkers)
  1870 /**
  1871  * Event seq for single selection change.
  1872  */
  1873 function selChangeSeq(aUnselectedID, aSelectedID)
  1875   if (!aUnselectedID) {
  1876     return [
  1877       new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
  1878       new invokerChecker(EVENT_SELECTION, aSelectedID)
  1879     ];
  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 [
  1886       new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
  1887       new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
  1888       new invokerChecker(EVENT_SELECTION, aSelectedID)
  1889     ],
  1891       new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
  1892       new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
  1893       new invokerChecker(EVENT_SELECTION, aSelectedID)
  1895   ];
  1898 /**
  1899  * Event seq for item removed form the selection.
  1900  */
  1901 function selRemoveSeq(aUnselectedID)
  1903   return [
  1904     new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
  1905     new invokerChecker(EVENT_SELECTION_REMOVE, aUnselectedID)
  1906   ];
  1909 /**
  1910  * Event seq for item added to the selection.
  1911  */
  1912 function selAddSeq(aSelectedID)
  1914   return [
  1915     new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
  1916     new invokerChecker(EVENT_SELECTION_ADD, aSelectedID)
  1917   ];
  1920 ////////////////////////////////////////////////////////////////////////////////
  1921 // Private implementation details.
  1922 ////////////////////////////////////////////////////////////////////////////////
  1925 ////////////////////////////////////////////////////////////////////////////////
  1926 // General
  1928 var gA11yEventListeners = {};
  1929 var gA11yEventApplicantsCount = 0;
  1931 var gA11yEventObserver =
  1933   observe: function observe(aSubject, aTopic, aData)
  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 + " ]";
  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;
  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;
  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;
  1993         eventFromDumpArea = false;
  1994         gLogger.log(info);
  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);
  2005 };
  2007 function listenA11yEvents(aStartToListen)
  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");
  2021 function addA11yEventListener(aEventType, aEventHandler)
  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);
  2032 function removeA11yEventListener(aEventType, aEventHandler)
  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];
  2049   return true;
  2052 /**
  2053  * Used to dump debug information.
  2054  */
  2055 var gLogger =
  2057   /**
  2058    * Return true if dump is enabled.
  2059    */
  2060   isEnabled: function debugOutput_isEnabled()
  2062     return gA11yEventDumpID || gA11yEventDumpToConsole ||
  2063       gA11yEventDumpToAppConsole;
  2064   },
  2066   /**
  2067    * Dump information into DOM and console if applicable.
  2068    */
  2069   log: function logger_log(aMsg)
  2071     this.logToConsole(aMsg);
  2072     this.logToAppConsole(aMsg);
  2073     this.logToDOM(aMsg);
  2074   },
  2076   /**
  2077    * Log message to DOM.
  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)
  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;
  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);
  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)
  2124     if (gA11yEventDumpToConsole)
  2125       dump("\n" + aMsg + "\n");
  2126   },
  2128   /**
  2129    * Log message to error console.
  2130    */
  2131   logToAppConsole: function logger_logToAppConsole(aMsg)
  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)
  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] == ";";
  2150 };
  2153 ////////////////////////////////////////////////////////////////////////////////
  2154 // Sequence
  2156 /**
  2157  * Base class of sequence item.
  2158  */
  2159 function sequenceItem(aProcessor, aEventType, aTarget, aItemID)
  2161   // private
  2163   this.startProcess = function sequenceItem_startProcess()
  2165     this.queue.invoke();
  2168   var item = this;
  2170   this.queue = new eventQueue();
  2171   this.queue.onFinish = function()
  2173     aProcessor.onProcessed();
  2174     return DO_NOT_FINISH_TEST;
  2177   var invoker = {
  2178     invoke: function invoker_invoke() {
  2179       return aProcessor.process();
  2180     },
  2181     getID: function invoker_getID()
  2183       return aItemID;
  2184     },
  2185     eventSeq: [ new invokerChecker(aEventType, aTarget) ]
  2186   };
  2188   this.queue.push(invoker);
  2191 ////////////////////////////////////////////////////////////////////////////////
  2192 // Event queue invokers
  2194 /**
  2195  * Invoker base class for prepare an action.
  2196  */
  2197 function synthAction(aNodeOrID, aEventsObj)
  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
  2212     for (var i = 0; i < scenarios.length; i++)
  2213       defineScenario(this, scenarios[i]);
  2216   this.getID = function synthAction_getID()
  2217     { return prettyName(aNodeOrID) + " action"; }

mercurial