accessible/tests/mochitest/events.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/accessible/tests/mochitest/events.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2218 @@
     1.4 +////////////////////////////////////////////////////////////////////////////////
     1.5 +// Constants
     1.6 +
     1.7 +const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT;
     1.8 +const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
     1.9 +const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
    1.10 +const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD;
    1.11 +const EVENT_DOCUMENT_LOAD_STOPPED = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_STOPPED;
    1.12 +const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
    1.13 +const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
    1.14 +const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
    1.15 +const EVENT_MENU_START = nsIAccessibleEvent.EVENT_MENU_START;
    1.16 +const EVENT_MENU_END = nsIAccessibleEvent.EVENT_MENU_END;
    1.17 +const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START;
    1.18 +const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END;
    1.19 +const EVENT_OBJECT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED;
    1.20 +const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
    1.21 +const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START;
    1.22 +const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION;
    1.23 +const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD;
    1.24 +const EVENT_SELECTION_REMOVE = nsIAccessibleEvent.EVENT_SELECTION_REMOVE;
    1.25 +const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
    1.26 +const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
    1.27 +const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
    1.28 +const EVENT_TEXT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED;
    1.29 +const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
    1.30 +const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
    1.31 +const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
    1.32 +const EVENT_TEXT_SELECTION_CHANGED = nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED;
    1.33 +const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
    1.34 +const EVENT_VIRTUALCURSOR_CHANGED = nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED;
    1.35 +
    1.36 +const kNotFromUserInput = 0;
    1.37 +const kFromUserInput = 1;
    1.38 +
    1.39 +////////////////////////////////////////////////////////////////////////////////
    1.40 +// General
    1.41 +
    1.42 +Components.utils.import("resource://gre/modules/Services.jsm");
    1.43 +
    1.44 +/**
    1.45 + * Set up this variable to dump events into DOM.
    1.46 + */
    1.47 +var gA11yEventDumpID = "";
    1.48 +
    1.49 +/**
    1.50 + * Set up this variable to dump event processing into console.
    1.51 + */
    1.52 +var gA11yEventDumpToConsole = false;
    1.53 +
    1.54 +/**
    1.55 + * Set up this variable to dump event processing into error console.
    1.56 + */
    1.57 +var gA11yEventDumpToAppConsole = false;
    1.58 +
    1.59 +/**
    1.60 + * Semicolon separated set of logging features.
    1.61 + */
    1.62 +var gA11yEventDumpFeature = "";
    1.63 +
    1.64 +/**
    1.65 + * Executes the function when requested event is handled.
    1.66 + *
    1.67 + * @param aEventType  [in] event type
    1.68 + * @param aTarget     [in] event target
    1.69 + * @param aFunc       [in] function to call when event is handled
    1.70 + * @param aContext    [in, optional] object in which context the function is
    1.71 + *                    called
    1.72 + * @param aArg1       [in, optional] argument passed into the function
    1.73 + * @param aArg2       [in, optional] argument passed into the function
    1.74 + */
    1.75 +function waitForEvent(aEventType, aTargetOrFunc, aFunc, aContext, aArg1, aArg2)
    1.76 +{
    1.77 +  var handler = {
    1.78 +    handleEvent: function handleEvent(aEvent) {
    1.79 +
    1.80 +      var target = aTargetOrFunc;
    1.81 +      if (typeof aTargetOrFunc == "function")
    1.82 +        target = aTargetOrFunc.call();
    1.83 +
    1.84 +      if (target) {
    1.85 +        if (target instanceof nsIAccessible &&
    1.86 +            target != aEvent.accessible)
    1.87 +          return;
    1.88 +
    1.89 +        if (target instanceof nsIDOMNode &&
    1.90 +            target != aEvent.DOMNode)
    1.91 +          return;
    1.92 +      }
    1.93 +
    1.94 +      unregisterA11yEventListener(aEventType, this);
    1.95 +
    1.96 +      window.setTimeout(
    1.97 +        function ()
    1.98 +        {
    1.99 +          aFunc.call(aContext, aArg1, aArg2);
   1.100 +        },
   1.101 +        0
   1.102 +      );
   1.103 +    }
   1.104 +  };
   1.105 +
   1.106 +  registerA11yEventListener(aEventType, handler);
   1.107 +}
   1.108 +
   1.109 +/**
   1.110 + * Generate mouse move over image map what creates image map accessible (async).
   1.111 + * See waitForImageMap() function.
   1.112 + */
   1.113 +function waveOverImageMap(aImageMapID)
   1.114 +{
   1.115 +  var imageMapNode = getNode(aImageMapID);
   1.116 +  synthesizeMouse(imageMapNode, 10, 10, { type: "mousemove" },
   1.117 +                  imageMapNode.ownerDocument.defaultView);
   1.118 +}
   1.119 +
   1.120 +/**
   1.121 + * Call the given function when the tree of the given image map is built.
   1.122 + */
   1.123 +function waitForImageMap(aImageMapID, aTestFunc)
   1.124 +{
   1.125 +  waveOverImageMap(aImageMapID);
   1.126 +
   1.127 +  var imageMapAcc = getAccessible(aImageMapID);
   1.128 +  if (imageMapAcc.firstChild)
   1.129 +    return aTestFunc();
   1.130 +
   1.131 +  waitForEvent(EVENT_REORDER, imageMapAcc, aTestFunc);
   1.132 +}
   1.133 +
   1.134 +/**
   1.135 + * Register accessibility event listener.
   1.136 + *
   1.137 + * @param aEventType     the accessible event type (see nsIAccessibleEvent for
   1.138 + *                       available constants).
   1.139 + * @param aEventHandler  event listener object, when accessible event of the
   1.140 + *                       given type is handled then 'handleEvent' method of
   1.141 + *                       this object is invoked with nsIAccessibleEvent object
   1.142 + *                       as the first argument.
   1.143 + */
   1.144 +function registerA11yEventListener(aEventType, aEventHandler)
   1.145 +{
   1.146 +  listenA11yEvents(true);
   1.147 +  addA11yEventListener(aEventType, aEventHandler);
   1.148 +}
   1.149 +
   1.150 +/**
   1.151 + * Unregister accessibility event listener. Must be called for every registered
   1.152 + * event listener (see registerA11yEventListener() function) when the listener
   1.153 + * is not needed.
   1.154 + */
   1.155 +function unregisterA11yEventListener(aEventType, aEventHandler)
   1.156 +{
   1.157 +  removeA11yEventListener(aEventType, aEventHandler);
   1.158 +  listenA11yEvents(false);
   1.159 +}
   1.160 +
   1.161 +
   1.162 +////////////////////////////////////////////////////////////////////////////////
   1.163 +// Event queue
   1.164 +
   1.165 +/**
   1.166 + * Return value of invoke method of invoker object. Indicates invoker was unable
   1.167 + * to prepare action.
   1.168 + */
   1.169 +const INVOKER_ACTION_FAILED = 1;
   1.170 +
   1.171 +/**
   1.172 + * Return value of eventQueue.onFinish. Indicates eventQueue should not finish
   1.173 + * tests.
   1.174 + */
   1.175 +const DO_NOT_FINISH_TEST = 1;
   1.176 +
   1.177 +/**
   1.178 + * Creates event queue for the given event type. The queue consists of invoker
   1.179 + * objects, each of them generates the event of the event type. When queue is
   1.180 + * started then every invoker object is asked to generate event after timeout.
   1.181 + * When event is caught then current invoker object is asked to check whether
   1.182 + * event was handled correctly.
   1.183 + *
   1.184 + * Invoker interface is:
   1.185 + *
   1.186 + *   var invoker = {
   1.187 + *     // Generates accessible event or event sequence. If returns
   1.188 + *     // INVOKER_ACTION_FAILED constant then stop tests.
   1.189 + *     invoke: function(){},
   1.190 + *
   1.191 + *     // [optional] Invoker's check of handled event for correctness.
   1.192 + *     check: function(aEvent){},
   1.193 + *
   1.194 + *     // [optional] Invoker's check before the next invoker is proceeded.
   1.195 + *     finalCheck: function(aEvent){},
   1.196 + *
   1.197 + *     // [optional] Is called when event of any registered type is handled.
   1.198 + *     debugCheck: function(aEvent){},
   1.199 + *
   1.200 + *     // [ignored if 'eventSeq' is defined] DOM node event is generated for
   1.201 + *     // (used in the case when invoker expects single event).
   1.202 + *     DOMNode getter: function() {},
   1.203 + *
   1.204 + *     // [optional] if true then event sequences are ignored (no failure if
   1.205 + *     // sequences are empty). Use you need to invoke an action, do some check
   1.206 + *     // after timeout and proceed a next invoker.
   1.207 + *     noEventsOnAction getter: function() {},
   1.208 + *
   1.209 + *     // Array of checker objects defining expected events on invoker's action.
   1.210 + *     //
   1.211 + *     // Checker object interface:
   1.212 + *     //
   1.213 + *     // var checker = {
   1.214 + *     //   * DOM or a11y event type. *
   1.215 + *     //   type getter: function() {},
   1.216 + *     //
   1.217 + *     //   * DOM node or accessible. *
   1.218 + *     //   target getter: function() {},
   1.219 + *     //
   1.220 + *     //   * DOM event phase (false - bubbling). *
   1.221 + *     //   phase getter: function() {},
   1.222 + *     //
   1.223 + *     //   * Callback, called to match handled event. *
   1.224 + *     //   match : function(aEvent) {},
   1.225 + *     //
   1.226 + *     //   * Callback, called when event is handled
   1.227 + *     //   check: function(aEvent) {},
   1.228 + *     //
   1.229 + *     //   * Checker ID *
   1.230 + *     //   getID: function() {},
   1.231 + *     //
   1.232 + *     //   * Event that don't have predefined order relative other events. *
   1.233 + *     //   async getter: function() {},
   1.234 + *     //
   1.235 + *     //   * Event that is not expected. *
   1.236 + *     //   unexpected getter: function() {},
   1.237 + *     //
   1.238 + *     //   * No other event of the same type is not allowed. *
   1.239 + *     //   unique getter: function() {}
   1.240 + *     // };
   1.241 + *     eventSeq getter() {},
   1.242 + *
   1.243 + *     // Array of checker objects defining unexpected events on invoker's
   1.244 + *     // action.
   1.245 + *     unexpectedEventSeq getter() {},
   1.246 + *
   1.247 + *     // The ID of invoker.
   1.248 + *     getID: function(){} // returns invoker ID
   1.249 + *   };
   1.250 + *
   1.251 + *   // Used to add a possible scenario of expected/unexpected events on
   1.252 + *   // invoker's action.
   1.253 + *  defineScenario(aInvokerObj, aEventSeq, aUnexpectedEventSeq)
   1.254 + *
   1.255 + *
   1.256 + * @param  aEventType  [in, optional] the default event type (isn't used if
   1.257 + *                      invoker defines eventSeq property).
   1.258 + */
   1.259 +function eventQueue(aEventType)
   1.260 +{
   1.261 +  // public
   1.262 +
   1.263 +  /**
   1.264 +   * Add invoker object into queue.
   1.265 +   */
   1.266 +  this.push = function eventQueue_push(aEventInvoker)
   1.267 +  {
   1.268 +    this.mInvokers.push(aEventInvoker);
   1.269 +  }
   1.270 +
   1.271 +  /**
   1.272 +   * Start the queue processing.
   1.273 +   */
   1.274 +  this.invoke = function eventQueue_invoke()
   1.275 +  {
   1.276 +    listenA11yEvents(true);
   1.277 +
   1.278 +    // XXX: Intermittent test_events_caretmove.html fails withouth timeout,
   1.279 +    // see bug 474952.
   1.280 +    this.processNextInvokerInTimeout(true);
   1.281 +  }
   1.282 +
   1.283 +  /**
   1.284 +   * This function is called when all events in the queue were handled.
   1.285 +   * Override it if you need to be notified of this.
   1.286 +   */
   1.287 +  this.onFinish = function eventQueue_finish()
   1.288 +  {
   1.289 +  }
   1.290 +
   1.291 +  // private
   1.292 +
   1.293 +  /**
   1.294 +   * Process next invoker.
   1.295 +   */
   1.296 +  this.processNextInvoker = function eventQueue_processNextInvoker()
   1.297 +  {
   1.298 +    // Some scenario was matched, we wait on next invoker processing.
   1.299 +    if (this.mNextInvokerStatus == kInvokerCanceled) {
   1.300 +      this.setInvokerStatus(kInvokerNotScheduled,
   1.301 +                            "scenario was matched, wait for next invoker activation");
   1.302 +      return;
   1.303 +    }
   1.304 +
   1.305 +    this.setInvokerStatus(kInvokerNotScheduled, "the next invoker is processed now");
   1.306 +
   1.307 +    // Finish processing of the current invoker if any.
   1.308 +    var testFailed = false;
   1.309 +
   1.310 +    var invoker = this.getInvoker();
   1.311 +    if (invoker) {
   1.312 +      if ("finalCheck" in invoker)
   1.313 +        invoker.finalCheck();
   1.314 +
   1.315 +      if (this.mScenarios && this.mScenarios.length) {
   1.316 +        var matchIdx = -1;
   1.317 +        for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
   1.318 +          var eventSeq = this.mScenarios[scnIdx];
   1.319 +          if (!this.areExpectedEventsLeft(eventSeq)) {
   1.320 +            for (var idx = 0; idx < eventSeq.length; idx++) {
   1.321 +              var checker = eventSeq[idx];
   1.322 +              if (checker.unexpected && checker.wasCaught ||
   1.323 +                  !checker.unexpected && checker.wasCaught != 1) {
   1.324 +                break;
   1.325 +              }
   1.326 +            }
   1.327 +
   1.328 +            // Ok, we have matched scenario. Report it was completed ok. In
   1.329 +            // case of empty scenario guess it was matched but if later we
   1.330 +            // find out that non empty scenario was matched then it will be
   1.331 +            // a final match.
   1.332 +            if (idx == eventSeq.length) {
   1.333 +              if (matchIdx != -1 && eventSeq.length > 0 &&
   1.334 +                  this.mScenarios[matchIdx].length > 0) {
   1.335 +                ok(false,
   1.336 +                   "We have a matched scenario at index " + matchIdx + " already.");
   1.337 +              }
   1.338 +
   1.339 +              if (matchIdx == -1 || eventSeq.length > 0)
   1.340 +                matchIdx = scnIdx;
   1.341 +
   1.342 +              // Report everythign is ok.
   1.343 +              for (var idx = 0; idx < eventSeq.length; idx++) {
   1.344 +                var checker = eventSeq[idx];
   1.345 +
   1.346 +                var typeStr = eventQueue.getEventTypeAsString(checker);
   1.347 +                var msg = "Test with ID = '" + this.getEventID(checker) +
   1.348 +                  "' succeed. ";
   1.349 +
   1.350 +                if (checker.unexpected)
   1.351 +                  ok(true, msg + "There's no unexpected " + typeStr + " event.");
   1.352 +                else
   1.353 +                  ok(true, msg + "Event " + typeStr + " was handled.");
   1.354 +              }
   1.355 +            }
   1.356 +          }
   1.357 +        }
   1.358 +
   1.359 +        // We don't have completely matched scenario. Report each failure/success
   1.360 +        // for every scenario.
   1.361 +        if (matchIdx == -1) {
   1.362 +          testFailed = true;
   1.363 +          for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
   1.364 +            var eventSeq = this.mScenarios[scnIdx];
   1.365 +            for (var idx = 0; idx < eventSeq.length; idx++) {
   1.366 +              var checker = eventSeq[idx];
   1.367 +
   1.368 +              var typeStr = eventQueue.getEventTypeAsString(checker);
   1.369 +              var msg = "Scenario #" + scnIdx + " of test with ID = '" +
   1.370 +                this.getEventID(checker) + "' failed. ";
   1.371 +
   1.372 +              if (checker.wasCaught > 1)
   1.373 +                ok(false, msg + "Dupe " + typeStr + " event.");
   1.374 +
   1.375 +              if (checker.unexpected) {
   1.376 +                if (checker.wasCaught)
   1.377 +                  ok(false, msg + "There's unexpected " + typeStr + " event.");
   1.378 +              } else if (!checker.wasCaught) {
   1.379 +                ok(false, msg + typeStr + " event was missed.");
   1.380 +              }
   1.381 +            }
   1.382 +          }
   1.383 +        }
   1.384 +      }
   1.385 +    }
   1.386 +
   1.387 +    this.clearEventHandler();
   1.388 +
   1.389 +    // Check if need to stop the test.
   1.390 +    if (testFailed || this.mIndex == this.mInvokers.length - 1) {
   1.391 +      listenA11yEvents(false);
   1.392 +
   1.393 +      var res = this.onFinish();
   1.394 +      if (res != DO_NOT_FINISH_TEST)
   1.395 +        SimpleTest.finish();
   1.396 +
   1.397 +      return;
   1.398 +    }
   1.399 +
   1.400 +    // Start processing of next invoker.
   1.401 +    invoker = this.getNextInvoker();
   1.402 +
   1.403 +    // Set up event listeners. Process a next invoker if no events were added.
   1.404 +    if (!this.setEventHandler(invoker)) {
   1.405 +      this.processNextInvoker();
   1.406 +      return;
   1.407 +    }
   1.408 +
   1.409 +    if (gLogger.isEnabled()) {
   1.410 +      gLogger.logToConsole("Event queue: \n  invoke: " + invoker.getID());
   1.411 +      gLogger.logToDOM("EQ: invoke: " + invoker.getID(), true);
   1.412 +    }
   1.413 +
   1.414 +    var infoText = "Invoke the '" + invoker.getID() + "' test { ";
   1.415 +    var scnCount = this.mScenarios ? this.mScenarios.length : 0;
   1.416 +    for (var scnIdx = 0; scnIdx < scnCount; scnIdx++) {
   1.417 +      infoText += "scenario #" + scnIdx + ": ";
   1.418 +      var eventSeq = this.mScenarios[scnIdx];
   1.419 +      for (var idx = 0; idx < eventSeq.length; idx++) {
   1.420 +        infoText += eventSeq[idx].unexpected ? "un" : "" +
   1.421 +          "expected '" + eventQueue.getEventTypeAsString(eventSeq[idx]) +
   1.422 +          "' event; ";
   1.423 +      }
   1.424 +    }
   1.425 +    infoText += " }";
   1.426 +    info(infoText);
   1.427 +
   1.428 +    if (invoker.invoke() == INVOKER_ACTION_FAILED) {
   1.429 +      // Invoker failed to prepare action, fail and finish tests.
   1.430 +      this.processNextInvoker();
   1.431 +      return;
   1.432 +    }
   1.433 +
   1.434 +    if (this.hasUnexpectedEventsScenario())
   1.435 +      this.processNextInvokerInTimeout(true);
   1.436 +  }
   1.437 +
   1.438 +  this.processNextInvokerInTimeout =
   1.439 +    function eventQueue_processNextInvokerInTimeout(aUncondProcess)
   1.440 +  {
   1.441 +    this.setInvokerStatus(kInvokerPending, "Process next invoker in timeout");
   1.442 +
   1.443 +    // No need to wait extra timeout when a) we know we don't need to do that
   1.444 +    // and b) there's no any single unexpected event.
   1.445 +    if (!aUncondProcess && this.areAllEventsExpected()) {
   1.446 +      // We need delay to avoid events coalesce from different invokers.
   1.447 +      var queue = this;
   1.448 +      SimpleTest.executeSoon(function() { queue.processNextInvoker(); });
   1.449 +      return;
   1.450 +    }
   1.451 +
   1.452 +    // Check in timeout invoker didn't fire registered events.
   1.453 +    window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 300,
   1.454 +                      this);
   1.455 +  }
   1.456 +
   1.457 +  /**
   1.458 +   * Handle events for the current invoker.
   1.459 +   */
   1.460 +  this.handleEvent = function eventQueue_handleEvent(aEvent)
   1.461 +  {
   1.462 +    var invoker = this.getInvoker();
   1.463 +    if (!invoker) // skip events before test was started
   1.464 +      return;
   1.465 +
   1.466 +    if (!this.mScenarios) {
   1.467 +      // Bad invoker object, error will be reported before processing of next
   1.468 +      // invoker in the queue.
   1.469 +      this.processNextInvoker();
   1.470 +      return;
   1.471 +    }
   1.472 +
   1.473 +    if ("debugCheck" in invoker)
   1.474 +      invoker.debugCheck(aEvent);
   1.475 +
   1.476 +    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
   1.477 +      var eventSeq = this.mScenarios[scnIdx];
   1.478 +      for (var idx = 0; idx < eventSeq.length; idx++) {
   1.479 +        var checker = eventSeq[idx];
   1.480 +
   1.481 +        // Search through handled expected events to report error if one of them
   1.482 +        // is handled for a second time.
   1.483 +        if (!checker.unexpected && (checker.wasCaught > 0) &&
   1.484 +            eventQueue.isSameEvent(checker, aEvent)) {
   1.485 +          checker.wasCaught++;
   1.486 +          continue;
   1.487 +        }
   1.488 +
   1.489 +        // Search through unexpected events, any match results in error report
   1.490 +        // after this invoker processing (in case of matched scenario only).
   1.491 +        if (checker.unexpected && eventQueue.compareEvents(checker, aEvent)) {
   1.492 +          checker.wasCaught++;
   1.493 +          continue;
   1.494 +        }
   1.495 +
   1.496 +        // Report an error if we hanlded not expected event of unique type
   1.497 +        // (i.e. event types are matched, targets differs).
   1.498 +        if (!checker.unexpected && checker.unique &&
   1.499 +            eventQueue.compareEventTypes(checker, aEvent)) {
   1.500 +          var isExppected = false;
   1.501 +          for (var jdx = 0; jdx < eventSeq.length; jdx++) {
   1.502 +            isExpected = eventQueue.compareEvents(eventSeq[jdx], aEvent);
   1.503 +            if (isExpected)
   1.504 +              break;
   1.505 +          }
   1.506 +
   1.507 +          if (!isExpected) {
   1.508 +            ok(false,
   1.509 +               "Unique type " +
   1.510 +               eventQueue.getEventTypeAsString(checker) + " event was handled.");
   1.511 +          }
   1.512 +        }
   1.513 +      }
   1.514 +    }
   1.515 +
   1.516 +    var hasMatchedCheckers = false;
   1.517 +    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
   1.518 +      var eventSeq = this.mScenarios[scnIdx];
   1.519 +
   1.520 +      // Check if handled event matches expected sync event.
   1.521 +      var nextChecker = this.getNextExpectedEvent(eventSeq);
   1.522 +      if (nextChecker) {
   1.523 +        if (eventQueue.compareEvents(nextChecker, aEvent)) {
   1.524 +          this.processMatchedChecker(aEvent, nextChecker, scnIdx, eventSeq.idx);
   1.525 +          hasMatchedCheckers = true;
   1.526 +          continue;
   1.527 +        }
   1.528 +      }
   1.529 +
   1.530 +      // Check if handled event matches any expected async events.
   1.531 +      for (idx = 0; idx < eventSeq.length; idx++) {
   1.532 +        if (!eventSeq[idx].unexpected && eventSeq[idx].async) {
   1.533 +          if (eventQueue.compareEvents(eventSeq[idx], aEvent)) {
   1.534 +            this.processMatchedChecker(aEvent, eventSeq[idx], scnIdx, idx);
   1.535 +            hasMatchedCheckers = true;
   1.536 +            break;
   1.537 +          }
   1.538 +        }
   1.539 +      }
   1.540 +    }
   1.541 +
   1.542 +    if (hasMatchedCheckers) {
   1.543 +      var invoker = this.getInvoker();
   1.544 +      if ("check" in invoker)
   1.545 +        invoker.check(aEvent);
   1.546 +    }
   1.547 +
   1.548 +    // If we don't have more events to wait then schedule next invoker.
   1.549 +    if (this.hasMatchedScenario()) {
   1.550 +      if (this.mNextInvokerStatus == kInvokerNotScheduled) {
   1.551 +        this.processNextInvokerInTimeout();
   1.552 +
   1.553 +      } else if (this.mNextInvokerStatus == kInvokerCanceled) {
   1.554 +        this.setInvokerStatus(kInvokerPending,
   1.555 +                              "Full match. Void the cancelation of next invoker processing");
   1.556 +      }
   1.557 +      return;
   1.558 +    }
   1.559 +
   1.560 +    // If we have scheduled a next invoker then cancel in case of match.
   1.561 +    if ((this.mNextInvokerStatus == kInvokerPending) && hasMatchedCheckers) {
   1.562 +      this.setInvokerStatus(kInvokerCanceled,
   1.563 +                            "Cancel the scheduled invoker in case of match");
   1.564 +    }
   1.565 +  }
   1.566 +
   1.567 +  // Helpers
   1.568 +  this.processMatchedChecker =
   1.569 +    function eventQueue_function(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx)
   1.570 +  {
   1.571 +    aMatchedChecker.wasCaught++;
   1.572 +
   1.573 +    if ("check" in aMatchedChecker)
   1.574 +      aMatchedChecker.check(aEvent);
   1.575 +
   1.576 +    eventQueue.logEvent(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx,
   1.577 +                        this.areExpectedEventsLeft(),
   1.578 +                        this.mNextInvokerStatus);
   1.579 +  }
   1.580 +
   1.581 +  this.getNextExpectedEvent =
   1.582 +    function eventQueue_getNextExpectedEvent(aEventSeq)
   1.583 +  {
   1.584 +    if (!("idx" in aEventSeq))
   1.585 +      aEventSeq.idx = 0;
   1.586 +
   1.587 +    while (aEventSeq.idx < aEventSeq.length &&
   1.588 +           (aEventSeq[aEventSeq.idx].unexpected ||
   1.589 +            aEventSeq[aEventSeq.idx].async ||
   1.590 +            aEventSeq[aEventSeq.idx].wasCaught > 0)) {
   1.591 +      aEventSeq.idx++;
   1.592 +    }
   1.593 +
   1.594 +    return aEventSeq.idx != aEventSeq.length ? aEventSeq[aEventSeq.idx] : null;
   1.595 +  }
   1.596 +
   1.597 +  this.areExpectedEventsLeft =
   1.598 +    function eventQueue_areExpectedEventsLeft(aScenario)
   1.599 +  {
   1.600 +    function scenarioHasUnhandledExpectedEvent(aEventSeq)
   1.601 +    {
   1.602 +      // Check if we have unhandled async (can be anywhere in the sequance) or
   1.603 +      // sync expcected events yet.
   1.604 +      for (var idx = 0; idx < aEventSeq.length; idx++) {
   1.605 +        if (!aEventSeq[idx].unexpected && !aEventSeq[idx].wasCaught)
   1.606 +          return true;
   1.607 +      }
   1.608 +
   1.609 +      return false;
   1.610 +    }
   1.611 +
   1.612 +    if (aScenario)
   1.613 +      return scenarioHasUnhandledExpectedEvent(aScenario);
   1.614 +
   1.615 +    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
   1.616 +      var eventSeq = this.mScenarios[scnIdx];
   1.617 +      if (scenarioHasUnhandledExpectedEvent(eventSeq))
   1.618 +        return true;
   1.619 +    }
   1.620 +    return false;
   1.621 +  }
   1.622 +
   1.623 +  this.areAllEventsExpected =
   1.624 +    function eventQueue_areAllEventsExpected()
   1.625 +  {
   1.626 +    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
   1.627 +      var eventSeq = this.mScenarios[scnIdx];
   1.628 +      for (var idx = 0; idx < eventSeq.length; idx++) {
   1.629 +        if (eventSeq[idx].unexpected)
   1.630 +          return false;
   1.631 +      }
   1.632 +    }
   1.633 +
   1.634 +    return true;
   1.635 +  }
   1.636 +
   1.637 +  this.isUnexpectedEventScenario =
   1.638 +    function eventQueue_isUnexpectedEventsScenario(aScenario)
   1.639 +  {
   1.640 +    for (var idx = 0; idx < aScenario.length; idx++) {
   1.641 +      if (!aScenario[idx].unexpected)
   1.642 +        break;
   1.643 +    }
   1.644 +
   1.645 +    return idx == aScenario.length;
   1.646 +  }
   1.647 +
   1.648 +  this.hasUnexpectedEventsScenario =
   1.649 +    function eventQueue_hasUnexpectedEventsScenario()
   1.650 +  {
   1.651 +    if (this.getInvoker().noEventsOnAction)
   1.652 +      return true;
   1.653 +
   1.654 +    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
   1.655 +      if (this.isUnexpectedEventScenario(this.mScenarios[scnIdx]))
   1.656 +        return true;
   1.657 +    }
   1.658 +
   1.659 +    return false;
   1.660 +  }
   1.661 +
   1.662 +  this.hasMatchedScenario =
   1.663 +    function eventQueue_hasMatchedScenario()
   1.664 +  {
   1.665 +    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
   1.666 +      var scn = this.mScenarios[scnIdx];
   1.667 +      if (!this.isUnexpectedEventScenario(scn) && !this.areExpectedEventsLeft(scn))
   1.668 +        return true;
   1.669 +    }
   1.670 +    return false;
   1.671 +  }
   1.672 +
   1.673 +  this.getInvoker = function eventQueue_getInvoker()
   1.674 +  {
   1.675 +    return this.mInvokers[this.mIndex];
   1.676 +  }
   1.677 +
   1.678 +  this.getNextInvoker = function eventQueue_getNextInvoker()
   1.679 +  {
   1.680 +    return this.mInvokers[++this.mIndex];
   1.681 +  }
   1.682 +
   1.683 +  this.setEventHandler = function eventQueue_setEventHandler(aInvoker)
   1.684 +  {
   1.685 +    if (!("scenarios" in aInvoker) || aInvoker.scenarios.length == 0) {
   1.686 +      var eventSeq = aInvoker.eventSeq;
   1.687 +      var unexpectedEventSeq = aInvoker.unexpectedEventSeq;
   1.688 +      if (!eventSeq && !unexpectedEventSeq && this.mDefEventType)
   1.689 +        eventSeq = [ new invokerChecker(this.mDefEventType, aInvoker.DOMNode) ];
   1.690 +
   1.691 +      if (eventSeq || unexpectedEventSeq)
   1.692 +        defineScenario(aInvoker, eventSeq, unexpectedEventSeq);
   1.693 +    }
   1.694 +
   1.695 +    if (aInvoker.noEventsOnAction)
   1.696 +      return true;
   1.697 +
   1.698 +    this.mScenarios = aInvoker.scenarios;
   1.699 +    if (!this.mScenarios || !this.mScenarios.length) {
   1.700 +      ok(false, "Broken invoker '" + aInvoker.getID() + "'");
   1.701 +      return false;
   1.702 +    }
   1.703 +
   1.704 +    // Register event listeners.
   1.705 +    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
   1.706 +      var eventSeq = this.mScenarios[scnIdx];
   1.707 +
   1.708 +      if (gLogger.isEnabled()) {
   1.709 +        var msg = "scenario #" + scnIdx +
   1.710 +          ", registered events number: " + eventSeq.length;
   1.711 +        gLogger.logToConsole(msg);
   1.712 +        gLogger.logToDOM(msg, true);
   1.713 +      }
   1.714 +
   1.715 +      // Do not warn about empty event sequances when more than one scenario
   1.716 +      // was registered.
   1.717 +      if (this.mScenarios.length == 1 && eventSeq.length == 0) {
   1.718 +        ok(false,
   1.719 +           "Broken scenario #" + scnIdx + " of invoker '" + aInvoker.getID() +
   1.720 +           "'. No registered events");
   1.721 +        return false;
   1.722 +      }
   1.723 +
   1.724 +      for (var idx = 0; idx < eventSeq.length; idx++)
   1.725 +        eventSeq[idx].wasCaught = 0;
   1.726 +
   1.727 +      for (var idx = 0; idx < eventSeq.length; idx++) {
   1.728 +        if (gLogger.isEnabled()) {
   1.729 +          var msg = "registered";
   1.730 +          if (eventSeq[idx].unexpected)
   1.731 +            msg += " unexpected";
   1.732 +          if (eventSeq[idx].async)
   1.733 +            msg += " async";
   1.734 +
   1.735 +          msg += ": event type: " +
   1.736 +            eventQueue.getEventTypeAsString(eventSeq[idx]) +
   1.737 +            ", target: " + eventQueue.getEventTargetDescr(eventSeq[idx], true);
   1.738 +
   1.739 +          gLogger.logToConsole(msg);
   1.740 +          gLogger.logToDOM(msg, true);
   1.741 +        }
   1.742 +
   1.743 +        var eventType = eventSeq[idx].type;
   1.744 +        if (typeof eventType == "string") {
   1.745 +          // DOM event
   1.746 +          var target = eventSeq[idx].target;
   1.747 +          if (!target) {
   1.748 +            ok(false, "no target for DOM event!");
   1.749 +            return false;
   1.750 +          }
   1.751 +          var phase = eventQueue.getEventPhase(eventSeq[idx]);
   1.752 +          target.ownerDocument.addEventListener(eventType, this, phase);
   1.753 +
   1.754 +        } else {
   1.755 +          // A11y event
   1.756 +          addA11yEventListener(eventType, this);
   1.757 +        }
   1.758 +      }
   1.759 +    }
   1.760 +
   1.761 +    return true;
   1.762 +  }
   1.763 +
   1.764 +  this.clearEventHandler = function eventQueue_clearEventHandler()
   1.765 +  {
   1.766 +    if (!this.mScenarios)
   1.767 +      return;
   1.768 +
   1.769 +    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
   1.770 +      var eventSeq = this.mScenarios[scnIdx];
   1.771 +      for (var idx = 0; idx < eventSeq.length; idx++) {
   1.772 +        var eventType = eventSeq[idx].type;
   1.773 +        if (typeof eventType == "string") {
   1.774 +          // DOM event
   1.775 +          var target = eventSeq[idx].target;
   1.776 +          var phase = eventQueue.getEventPhase(eventSeq[idx]);
   1.777 +          target.ownerDocument.removeEventListener(eventType, this, phase);
   1.778 +
   1.779 +        } else {
   1.780 +          // A11y event
   1.781 +          removeA11yEventListener(eventType, this);
   1.782 +        }
   1.783 +      }
   1.784 +    }
   1.785 +    this.mScenarios = null;
   1.786 +  }
   1.787 +
   1.788 +  this.getEventID = function eventQueue_getEventID(aChecker)
   1.789 +  {
   1.790 +    if ("getID" in aChecker)
   1.791 +      return aChecker.getID();
   1.792 +
   1.793 +    var invoker = this.getInvoker();
   1.794 +    return invoker.getID();
   1.795 +  }
   1.796 +
   1.797 +  this.setInvokerStatus = function eventQueue_setInvokerStatus(aStatus, aLogMsg)
   1.798 +  {
   1.799 +    this.mNextInvokerStatus = aStatus;
   1.800 +
   1.801 +    // Uncomment it to debug invoker processing logic.
   1.802 +    //gLogger.log(eventQueue.invokerStatusToMsg(aStatus, aLogMsg));
   1.803 +  }
   1.804 +
   1.805 +  this.mDefEventType = aEventType;
   1.806 +
   1.807 +  this.mInvokers = new Array();
   1.808 +  this.mIndex = -1;
   1.809 +  this.mScenarios = null;
   1.810 +
   1.811 +  this.mNextInvokerStatus = kInvokerNotScheduled;
   1.812 +}
   1.813 +
   1.814 +////////////////////////////////////////////////////////////////////////////////
   1.815 +// eventQueue static members and constants
   1.816 +
   1.817 +const kInvokerNotScheduled = 0;
   1.818 +const kInvokerPending = 1;
   1.819 +const kInvokerCanceled = 2;
   1.820 +
   1.821 +eventQueue.getEventTypeAsString =
   1.822 +  function eventQueue_getEventTypeAsString(aEventOrChecker)
   1.823 +{
   1.824 +  if (aEventOrChecker instanceof nsIDOMEvent)
   1.825 +    return aEventOrChecker.type;
   1.826 +
   1.827 +  if (aEventOrChecker instanceof nsIAccessibleEvent)
   1.828 +    return eventTypeToString(aEventOrChecker.eventType);
   1.829 +
   1.830 +  return (typeof aEventOrChecker.type == "string") ?
   1.831 +    aEventOrChecker.type : eventTypeToString(aEventOrChecker.type);
   1.832 +}
   1.833 +
   1.834 +eventQueue.getEventTargetDescr =
   1.835 +  function eventQueue_getEventTargetDescr(aEventOrChecker, aDontForceTarget)
   1.836 +{
   1.837 +  if (aEventOrChecker instanceof nsIDOMEvent)
   1.838 +    return prettyName(aEventOrChecker.originalTarget);
   1.839 +
   1.840 +  if (aEventOrChecker instanceof nsIDOMEvent)
   1.841 +    return prettyName(aEventOrChecker.accessible);
   1.842 +
   1.843 +  var descr = aEventOrChecker.targetDescr;
   1.844 +  if (descr)
   1.845 +    return descr;
   1.846 +
   1.847 +  if (aDontForceTarget)
   1.848 +    return "no target description";
   1.849 +
   1.850 +  var target = ("target" in aEventOrChecker) ? aEventOrChecker.target : null;
   1.851 +  return prettyName(target);
   1.852 +}
   1.853 +
   1.854 +eventQueue.getEventPhase = function eventQueue_getEventPhase(aChecker)
   1.855 +{
   1.856 +  return ("phase" in aChecker) ? aChecker.phase : true;
   1.857 +}
   1.858 +
   1.859 +eventQueue.compareEventTypes =
   1.860 +  function eventQueue_compareEventTypes(aChecker, aEvent)
   1.861 +{
   1.862 +  var eventType = (aEvent instanceof nsIDOMEvent) ?
   1.863 +    aEvent.type : aEvent.eventType;
   1.864 +  return aChecker.type == eventType;
   1.865 +}
   1.866 +
   1.867 +eventQueue.compareEvents = function eventQueue_compareEvents(aChecker, aEvent)
   1.868 +{
   1.869 +  if (!eventQueue.compareEventTypes(aChecker, aEvent))
   1.870 +    return false;
   1.871 +
   1.872 +  // If checker provides "match" function then allow the checker to decide
   1.873 +  // whether event is matched.
   1.874 +  if ("match" in aChecker)
   1.875 +    return aChecker.match(aEvent);
   1.876 +
   1.877 +  var target1 = aChecker.target;
   1.878 +  if (target1 instanceof nsIAccessible) {
   1.879 +    var target2 = (aEvent instanceof nsIDOMEvent) ?
   1.880 +      getAccessible(aEvent.target) : aEvent.accessible;
   1.881 +
   1.882 +    return target1 == target2;
   1.883 +  }
   1.884 +
   1.885 +  // If original target isn't suitable then extend interface to support target
   1.886 +  // (original target is used in test_elm_media.html).
   1.887 +  var target2 = (aEvent instanceof nsIDOMEvent) ?
   1.888 +    aEvent.originalTarget : aEvent.DOMNode;
   1.889 +  return target1 == target2;
   1.890 +}
   1.891 +
   1.892 +eventQueue.isSameEvent = function eventQueue_isSameEvent(aChecker, aEvent)
   1.893 +{
   1.894 +  // We don't have stored info about handled event other than its type and
   1.895 +  // target, thus we should filter text change and state change events since
   1.896 +  // they may occur on the same element because of complex changes.
   1.897 +  return this.compareEvents(aChecker, aEvent) &&
   1.898 +    !(aEvent instanceof nsIAccessibleTextChangeEvent) &&
   1.899 +    !(aEvent instanceof nsIAccessibleStateChangeEvent);
   1.900 +}
   1.901 +
   1.902 +eventQueue.invokerStatusToMsg =
   1.903 +  function eventQueue_invokerStatusToMsg(aInvokerStatus, aMsg)
   1.904 +{
   1.905 +  var msg = "invoker status: ";
   1.906 +  switch (aInvokerStatus) {
   1.907 +    case kInvokerNotScheduled:
   1.908 +      msg += "not scheduled";
   1.909 +      break;
   1.910 +    case kInvokerPending:
   1.911 +      msg += "pending";
   1.912 +      break;
   1.913 +    case kInvokerCanceled:
   1.914 +      msg += "canceled";
   1.915 +      break;
   1.916 +  }
   1.917 +
   1.918 +  if (aMsg)
   1.919 +    msg += " (" + aMsg + ")";
   1.920 +
   1.921 +  return msg;
   1.922 +}
   1.923 +
   1.924 +eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
   1.925 +                                                   aScenarioIdx, aEventIdx,
   1.926 +                                                   aAreExpectedEventsLeft,
   1.927 +                                                   aInvokerStatus)
   1.928 +{
   1.929 +  // Dump DOM event information. Skip a11y event since it is dumped by
   1.930 +  // gA11yEventObserver.
   1.931 +  if (aOrigEvent instanceof nsIDOMEvent) {
   1.932 +    var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent);
   1.933 +    info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent);
   1.934 +    gLogger.logToDOM(info);
   1.935 +  }
   1.936 +
   1.937 +  var infoMsg = "unhandled expected events: " + aAreExpectedEventsLeft +
   1.938 +    ", "  + eventQueue.invokerStatusToMsg(aInvokerStatus);
   1.939 +
   1.940 +  var currType = eventQueue.getEventTypeAsString(aMatchedChecker);
   1.941 +  var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker);
   1.942 +  var consoleMsg = "*****\nScenario " + aScenarioIdx + 
   1.943 +    ", event " + aEventIdx + " matched: " + currType + "\n" + infoMsg + "\n*****";
   1.944 +  gLogger.logToConsole(consoleMsg);
   1.945 +
   1.946 +  var emphText = "matched ";
   1.947 +  var msg = "EQ event, type: " + currType + ", target: " + currTargetDescr +
   1.948 +    ", " + infoMsg;
   1.949 +  gLogger.logToDOM(msg, true, emphText);
   1.950 +}
   1.951 +
   1.952 +
   1.953 +////////////////////////////////////////////////////////////////////////////////
   1.954 +// Action sequence
   1.955 +
   1.956 +/**
   1.957 + * Deal with action sequence. Used when you need to execute couple of actions
   1.958 + * each after other one.
   1.959 + */
   1.960 +function sequence()
   1.961 +{
   1.962 +  /**
   1.963 +   * Append new sequence item.
   1.964 +   *
   1.965 +   * @param  aProcessor  [in] object implementing interface
   1.966 +   *                      {
   1.967 +   *                        // execute item action
   1.968 +   *                        process: function() {},
   1.969 +   *                        // callback, is called when item was processed
   1.970 +   *                        onProcessed: function() {}
   1.971 +   *                      };
   1.972 +   * @param  aEventType  [in] event type of expected event on item action
   1.973 +   * @param  aTarget     [in] event target of expected event on item action
   1.974 +   * @param  aItemID     [in] identifier of item
   1.975 +   */
   1.976 +  this.append = function sequence_append(aProcessor, aEventType, aTarget,
   1.977 +                                         aItemID)
   1.978 +  {
   1.979 +    var item = new sequenceItem(aProcessor, aEventType, aTarget, aItemID);
   1.980 +    this.items.push(item);
   1.981 +  }
   1.982 +
   1.983 +  /**
   1.984 +   * Process next sequence item.
   1.985 +   */
   1.986 +  this.processNext = function sequence_processNext()
   1.987 +  {
   1.988 +    this.idx++;
   1.989 +    if (this.idx >= this.items.length) {
   1.990 +      ok(false, "End of sequence: nothing to process!");
   1.991 +      SimpleTest.finish();
   1.992 +      return;
   1.993 +    }
   1.994 +
   1.995 +    this.items[this.idx].startProcess();
   1.996 +  }
   1.997 +
   1.998 +  this.items = new Array();
   1.999 +  this.idx = -1;
  1.1000 +}
  1.1001 +
  1.1002 +
  1.1003 +////////////////////////////////////////////////////////////////////////////////
  1.1004 +// Event queue invokers
  1.1005 +
  1.1006 +/**
  1.1007 + * Defines a scenario of expected/unexpected events. Each invoker can have
  1.1008 + * one or more scenarios of events. Only one scenario must be completed.
  1.1009 + */
  1.1010 +function defineScenario(aInvoker, aEventSeq, aUnexpectedEventSeq)
  1.1011 +{
  1.1012 +  if (!("scenarios" in aInvoker))
  1.1013 +    aInvoker.scenarios = new Array();
  1.1014 +
  1.1015 +  // Create unified event sequence concatenating expected and unexpected
  1.1016 +  // events.
  1.1017 +  if (!aEventSeq)
  1.1018 +    aEventSeq = [];
  1.1019 +
  1.1020 +  for (var idx = 0; idx < aEventSeq.length; idx++) {
  1.1021 +    aEventSeq[idx].unexpected |= false;
  1.1022 +    aEventSeq[idx].async |= false;
  1.1023 +  }
  1.1024 +
  1.1025 +  if (aUnexpectedEventSeq) {
  1.1026 +    for (var idx = 0; idx < aUnexpectedEventSeq.length; idx++) {
  1.1027 +      aUnexpectedEventSeq[idx].unexpected = true;
  1.1028 +      aUnexpectedEventSeq[idx].async = false;
  1.1029 +    }
  1.1030 +
  1.1031 +    aEventSeq = aEventSeq.concat(aUnexpectedEventSeq);
  1.1032 +  }
  1.1033 +
  1.1034 +  aInvoker.scenarios.push(aEventSeq);
  1.1035 +}
  1.1036 +
  1.1037 +
  1.1038 +/**
  1.1039 + * Invokers defined below take a checker object (or array of checker objects).
  1.1040 + * An invoker listens for default event type registered in event queue object
  1.1041 + * until its checker is provided.
  1.1042 + *
  1.1043 + * Note, checker object or array of checker objects is optional.
  1.1044 + */
  1.1045 +
  1.1046 +/**
  1.1047 + * Click invoker.
  1.1048 + */
  1.1049 +function synthClick(aNodeOrID, aCheckerOrEventSeq, aArgs)
  1.1050 +{
  1.1051 +  this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
  1.1052 +
  1.1053 +  this.invoke = function synthClick_invoke()
  1.1054 +  {
  1.1055 +    var targetNode = this.DOMNode;
  1.1056 +    if (targetNode instanceof nsIDOMDocument) {
  1.1057 +      targetNode =
  1.1058 +        this.DOMNode.body ? this.DOMNode.body : this.DOMNode.documentElement;
  1.1059 +    }
  1.1060 +
  1.1061 +    // Scroll the node into view, otherwise synth click may fail.
  1.1062 +    if (targetNode instanceof nsIDOMHTMLElement) {
  1.1063 +      targetNode.scrollIntoView(true);
  1.1064 +    } else if (targetNode instanceof nsIDOMXULElement) {
  1.1065 +      var targetAcc = getAccessible(targetNode);
  1.1066 +      targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
  1.1067 +    }
  1.1068 +
  1.1069 +    var x = 1, y = 1;
  1.1070 +    if (aArgs && ("where" in aArgs) && aArgs.where == "right") {
  1.1071 +      if (targetNode instanceof nsIDOMHTMLElement)
  1.1072 +        x = targetNode.offsetWidth - 1;
  1.1073 +      else if (targetNode instanceof nsIDOMXULElement)
  1.1074 +        x = targetNode.boxObject.width - 1;
  1.1075 +    }
  1.1076 +    synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {});
  1.1077 +  }
  1.1078 +
  1.1079 +  this.finalCheck = function synthClick_finalCheck()
  1.1080 +  {
  1.1081 +    // Scroll top window back.
  1.1082 +    window.top.scrollTo(0, 0);
  1.1083 +  }
  1.1084 +
  1.1085 +  this.getID = function synthClick_getID()
  1.1086 +  {
  1.1087 +    return prettyName(aNodeOrID) + " click";
  1.1088 +  }
  1.1089 +}
  1.1090 +
  1.1091 +/**
  1.1092 + * Mouse move invoker.
  1.1093 + */
  1.1094 +function synthMouseMove(aID, aCheckerOrEventSeq)
  1.1095 +{
  1.1096 +  this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
  1.1097 +
  1.1098 +  this.invoke = function synthMouseMove_invoke()
  1.1099 +  {
  1.1100 +    synthesizeMouse(this.DOMNode, 1, 1, { type: "mousemove" });
  1.1101 +    synthesizeMouse(this.DOMNode, 2, 2, { type: "mousemove" });
  1.1102 +  }
  1.1103 +
  1.1104 +  this.getID = function synthMouseMove_getID()
  1.1105 +  {
  1.1106 +    return prettyName(aID) + " mouse move";
  1.1107 +  }
  1.1108 +}
  1.1109 +
  1.1110 +/**
  1.1111 + * General key press invoker.
  1.1112 + */
  1.1113 +function synthKey(aNodeOrID, aKey, aArgs, aCheckerOrEventSeq)
  1.1114 +{
  1.1115 +  this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
  1.1116 +
  1.1117 +  this.invoke = function synthKey_invoke()
  1.1118 +  {
  1.1119 +    synthesizeKey(this.mKey, this.mArgs, this.mWindow);
  1.1120 +  }
  1.1121 +
  1.1122 +  this.getID = function synthKey_getID()
  1.1123 +  {
  1.1124 +    var key = this.mKey;
  1.1125 +    switch (this.mKey) {
  1.1126 +      case "VK_TAB":
  1.1127 +        key = "tab";
  1.1128 +        break;
  1.1129 +      case "VK_DOWN":
  1.1130 +        key = "down";
  1.1131 +        break;
  1.1132 +      case "VK_UP":
  1.1133 +        key = "up";
  1.1134 +        break;
  1.1135 +      case "VK_LEFT":
  1.1136 +        key = "left";
  1.1137 +        break;
  1.1138 +      case "VK_RIGHT":
  1.1139 +        key = "right";
  1.1140 +        break;
  1.1141 +      case "VK_HOME":
  1.1142 +        key = "home";
  1.1143 +        break;
  1.1144 +      case "VK_END":
  1.1145 +        key = "end";
  1.1146 +        break;
  1.1147 +      case "VK_ESCAPE":
  1.1148 +        key = "escape";
  1.1149 +        break;
  1.1150 +      case "VK_RETURN":
  1.1151 +        key = "enter";
  1.1152 +        break;
  1.1153 +    }
  1.1154 +    if (aArgs) {
  1.1155 +      if (aArgs.shiftKey)
  1.1156 +        key += " shift";
  1.1157 +      if (aArgs.ctrlKey)
  1.1158 +        key += " ctrl";
  1.1159 +      if (aArgs.altKey)
  1.1160 +        key += " alt";
  1.1161 +    }
  1.1162 +    return prettyName(aNodeOrID) + " '" + key + " ' key";
  1.1163 +  }
  1.1164 +
  1.1165 +  this.mKey = aKey;
  1.1166 +  this.mArgs = aArgs ? aArgs : {};
  1.1167 +  this.mWindow = aArgs ? aArgs.window : null;
  1.1168 +}
  1.1169 +
  1.1170 +/**
  1.1171 + * Tab key invoker.
  1.1172 + */
  1.1173 +function synthTab(aNodeOrID, aCheckerOrEventSeq, aWindow)
  1.1174 +{
  1.1175 +  this.__proto__ = new synthKey(aNodeOrID, "VK_TAB",
  1.1176 +                                { shiftKey: false, window: aWindow },
  1.1177 +                                aCheckerOrEventSeq);
  1.1178 +}
  1.1179 +
  1.1180 +/**
  1.1181 + * Shift tab key invoker.
  1.1182 + */
  1.1183 +function synthShiftTab(aNodeOrID, aCheckerOrEventSeq)
  1.1184 +{
  1.1185 +  this.__proto__ = new synthKey(aNodeOrID, "VK_TAB", { shiftKey: true },
  1.1186 +                                aCheckerOrEventSeq);
  1.1187 +}
  1.1188 +
  1.1189 +/**
  1.1190 + * Escape key invoker.
  1.1191 + */
  1.1192 +function synthEscapeKey(aNodeOrID, aCheckerOrEventSeq)
  1.1193 +{
  1.1194 +  this.__proto__ = new synthKey(aNodeOrID, "VK_ESCAPE", null,
  1.1195 +                                aCheckerOrEventSeq);
  1.1196 +}
  1.1197 +
  1.1198 +/**
  1.1199 + * Down arrow key invoker.
  1.1200 + */
  1.1201 +function synthDownKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
  1.1202 +{
  1.1203 +  this.__proto__ = new synthKey(aNodeOrID, "VK_DOWN", aArgs,
  1.1204 +                                aCheckerOrEventSeq);
  1.1205 +}
  1.1206 +
  1.1207 +/**
  1.1208 + * Up arrow key invoker.
  1.1209 + */
  1.1210 +function synthUpKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
  1.1211 +{
  1.1212 +  this.__proto__ = new synthKey(aNodeOrID, "VK_UP", aArgs,
  1.1213 +                                aCheckerOrEventSeq);
  1.1214 +}
  1.1215 +
  1.1216 +/**
  1.1217 + * Left arrow key invoker.
  1.1218 + */
  1.1219 +function synthLeftKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
  1.1220 +{
  1.1221 +  this.__proto__ = new synthKey(aNodeOrID, "VK_LEFT", aArgs, aCheckerOrEventSeq);
  1.1222 +}
  1.1223 +
  1.1224 +/**
  1.1225 + * Right arrow key invoker.
  1.1226 + */
  1.1227 +function synthRightKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
  1.1228 +{
  1.1229 +  this.__proto__ = new synthKey(aNodeOrID, "VK_RIGHT", aArgs, aCheckerOrEventSeq);
  1.1230 +}
  1.1231 +
  1.1232 +/**
  1.1233 + * Home key invoker.
  1.1234 + */
  1.1235 +function synthHomeKey(aNodeOrID, aCheckerOrEventSeq)
  1.1236 +{
  1.1237 +  this.__proto__ = new synthKey(aNodeOrID, "VK_HOME", null, aCheckerOrEventSeq);
  1.1238 +}
  1.1239 +
  1.1240 +/**
  1.1241 + * End key invoker.
  1.1242 + */
  1.1243 +function synthEndKey(aNodeOrID, aCheckerOrEventSeq)
  1.1244 +{
  1.1245 +  this.__proto__ = new synthKey(aNodeOrID, "VK_END", null, aCheckerOrEventSeq);
  1.1246 +}
  1.1247 +
  1.1248 +/**
  1.1249 + * Enter key invoker
  1.1250 + */
  1.1251 +function synthEnterKey(aID, aCheckerOrEventSeq)
  1.1252 +{
  1.1253 +  this.__proto__ = new synthKey(aID, "VK_RETURN", null, aCheckerOrEventSeq);
  1.1254 +}
  1.1255 +
  1.1256 +/**
  1.1257 + * Synth alt + down arrow to open combobox.
  1.1258 + */
  1.1259 +function synthOpenComboboxKey(aID, aCheckerOrEventSeq)
  1.1260 +{
  1.1261 +  this.__proto__ = new synthDownKey(aID, aCheckerOrEventSeq, { altKey: true });
  1.1262 +
  1.1263 +  this.getID = function synthOpenComboboxKey_getID()
  1.1264 +  {
  1.1265 +    return "open combobox (atl + down arrow) " + prettyName(aID);
  1.1266 +  }
  1.1267 +}
  1.1268 +
  1.1269 +/**
  1.1270 + * Focus invoker.
  1.1271 + */
  1.1272 +function synthFocus(aNodeOrID, aCheckerOrEventSeq)
  1.1273 +{
  1.1274 +  var checkerOfEventSeq =
  1.1275 +    aCheckerOrEventSeq ? aCheckerOrEventSeq : new focusChecker(aNodeOrID);
  1.1276 +  this.__proto__ = new synthAction(aNodeOrID, checkerOfEventSeq);
  1.1277 +
  1.1278 +  this.invoke = function synthFocus_invoke()
  1.1279 +  {
  1.1280 +    if (this.DOMNode instanceof Components.interfaces.nsIDOMNSEditableElement &&
  1.1281 +        this.DOMNode.editor ||
  1.1282 +        this.DOMNode instanceof Components.interfaces.nsIDOMXULTextBoxElement) {
  1.1283 +      this.DOMNode.selectionStart = this.DOMNode.selectionEnd = this.DOMNode.value.length;
  1.1284 +    }
  1.1285 +    this.DOMNode.focus();
  1.1286 +  }
  1.1287 +
  1.1288 +  this.getID = function synthFocus_getID() 
  1.1289 +  { 
  1.1290 +    return prettyName(aNodeOrID) + " focus";
  1.1291 +  }
  1.1292 +}
  1.1293 +
  1.1294 +/**
  1.1295 + * Focus invoker. Focus the HTML body of content document of iframe.
  1.1296 + */
  1.1297 +function synthFocusOnFrame(aNodeOrID, aCheckerOrEventSeq)
  1.1298 +{
  1.1299 +  var frameDoc = getNode(aNodeOrID).contentDocument;
  1.1300 +  var checkerOrEventSeq =
  1.1301 +    aCheckerOrEventSeq ? aCheckerOrEventSeq : new focusChecker(frameDoc);
  1.1302 +  this.__proto__ = new synthAction(frameDoc, checkerOrEventSeq);
  1.1303 +
  1.1304 +  this.invoke = function synthFocus_invoke()
  1.1305 +  {
  1.1306 +    this.DOMNode.body.focus();
  1.1307 +  }
  1.1308 +
  1.1309 +  this.getID = function synthFocus_getID() 
  1.1310 +  { 
  1.1311 +    return prettyName(aNodeOrID) + " frame document focus";
  1.1312 +  }
  1.1313 +}
  1.1314 +
  1.1315 +/**
  1.1316 + * Change the current item when the widget doesn't have a focus.
  1.1317 + */
  1.1318 +function changeCurrentItem(aID, aItemID)
  1.1319 +{
  1.1320 +  this.eventSeq = [ new nofocusChecker() ];
  1.1321 +
  1.1322 +  this.invoke = function changeCurrentItem_invoke()
  1.1323 +  {
  1.1324 +    var controlNode = getNode(aID);
  1.1325 +    var itemNode = getNode(aItemID);
  1.1326 +
  1.1327 +    // HTML
  1.1328 +    if (controlNode.localName == "input") {
  1.1329 +      if (controlNode.checked)
  1.1330 +        this.reportError();
  1.1331 +
  1.1332 +      controlNode.checked = true;
  1.1333 +      return;
  1.1334 +    }
  1.1335 +
  1.1336 +    if (controlNode.localName == "select") {
  1.1337 +      if (controlNode.selectedIndex == itemNode.index)
  1.1338 +        this.reportError();
  1.1339 +
  1.1340 +      controlNode.selectedIndex = itemNode.index;
  1.1341 +      return;
  1.1342 +    }
  1.1343 +
  1.1344 +    // XUL
  1.1345 +    if (controlNode.localName == "tree") {
  1.1346 +      if (controlNode.currentIndex == aItemID)
  1.1347 +        this.reportError();
  1.1348 +
  1.1349 +      controlNode.currentIndex = aItemID;
  1.1350 +      return;
  1.1351 +    }
  1.1352 +
  1.1353 +    if (controlNode.localName == "menulist") {
  1.1354 +      if (controlNode.selectedItem == itemNode)
  1.1355 +        this.reportError();
  1.1356 +
  1.1357 +      controlNode.selectedItem = itemNode;
  1.1358 +      return;
  1.1359 +    }
  1.1360 +
  1.1361 +    if (controlNode.currentItem == itemNode)
  1.1362 +      ok(false, "Error in test: proposed current item is already current" + prettyName(aID));
  1.1363 +
  1.1364 +    controlNode.currentItem = itemNode;
  1.1365 +  }
  1.1366 +
  1.1367 +  this.getID = function changeCurrentItem_getID()
  1.1368 +  {
  1.1369 +    return "current item change for " + prettyName(aID);
  1.1370 +  }
  1.1371 +
  1.1372 +  this.reportError = function changeCurrentItem_reportError()
  1.1373 +  {
  1.1374 +    ok(false,
  1.1375 +       "Error in test: proposed current item '" + aItemID + "' is already current");
  1.1376 +  }
  1.1377 +}
  1.1378 +
  1.1379 +/**
  1.1380 + * Toggle top menu invoker.
  1.1381 + */
  1.1382 +function toggleTopMenu(aID, aCheckerOrEventSeq)
  1.1383 +{
  1.1384 +  this.__proto__ = new synthKey(aID, "VK_ALT", null,
  1.1385 +                                aCheckerOrEventSeq);
  1.1386 +
  1.1387 +  this.getID = function toggleTopMenu_getID()
  1.1388 +  {
  1.1389 +    return "toggle top menu on " + prettyName(aID);
  1.1390 +  }
  1.1391 +}
  1.1392 +
  1.1393 +/**
  1.1394 + * Context menu invoker.
  1.1395 + */
  1.1396 +function synthContextMenu(aID, aCheckerOrEventSeq)
  1.1397 +{
  1.1398 +  this.__proto__ = new synthClick(aID, aCheckerOrEventSeq,
  1.1399 +                                  { button: 0, type: "contextmenu" });
  1.1400 +
  1.1401 +  this.getID = function synthContextMenu_getID()
  1.1402 +  {
  1.1403 +    return "context menu on " + prettyName(aID);
  1.1404 +  }
  1.1405 +}
  1.1406 +
  1.1407 +/**
  1.1408 + * Open combobox, autocomplete and etc popup, check expandable states.
  1.1409 + */
  1.1410 +function openCombobox(aComboboxID)
  1.1411 +{
  1.1412 +  this.eventSeq = [
  1.1413 +    new stateChangeChecker(STATE_EXPANDED, false, true, aComboboxID)
  1.1414 +  ];
  1.1415 +
  1.1416 +  this.invoke = function openCombobox_invoke()
  1.1417 +  {
  1.1418 +    getNode(aComboboxID).focus();
  1.1419 +    synthesizeKey("VK_DOWN", { altKey: true });
  1.1420 +  }
  1.1421 +
  1.1422 +  this.getID = function openCombobox_getID()
  1.1423 +  {
  1.1424 +    return "open combobox " + prettyName(aComboboxID);
  1.1425 +  }
  1.1426 +}
  1.1427 +
  1.1428 +/**
  1.1429 + * Close combobox, autocomplete and etc popup, check expandable states.
  1.1430 + */
  1.1431 +function closeCombobox(aComboboxID)
  1.1432 +{
  1.1433 +  this.eventSeq = [
  1.1434 +    new stateChangeChecker(STATE_EXPANDED, false, false, aComboboxID)
  1.1435 +  ];
  1.1436 +
  1.1437 +  this.invoke = function closeCombobox_invoke()
  1.1438 +  {
  1.1439 +    synthesizeKey("VK_ESCAPE", { });
  1.1440 +  }
  1.1441 +
  1.1442 +  this.getID = function closeCombobox_getID()
  1.1443 +  {
  1.1444 +    return "close combobox " + prettyName(aComboboxID);
  1.1445 +  }
  1.1446 +}
  1.1447 +
  1.1448 +
  1.1449 +/**
  1.1450 + * Select all invoker.
  1.1451 + */
  1.1452 +function synthSelectAll(aNodeOrID, aCheckerOrEventSeq)
  1.1453 +{
  1.1454 +  this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
  1.1455 +
  1.1456 +  this.invoke = function synthSelectAll_invoke()
  1.1457 +  {
  1.1458 +    if (this.DOMNode instanceof Components.interfaces.nsIDOMHTMLInputElement ||
  1.1459 +        this.DOMNode instanceof Components.interfaces.nsIDOMXULTextBoxElement) {
  1.1460 +      this.DOMNode.select();
  1.1461 +
  1.1462 +    } else {
  1.1463 +      window.getSelection().selectAllChildren(this.DOMNode);
  1.1464 +    }
  1.1465 +  }
  1.1466 +
  1.1467 +  this.getID = function synthSelectAll_getID()
  1.1468 +  {
  1.1469 +    return aNodeOrID + " selectall";
  1.1470 +  }
  1.1471 +}
  1.1472 +
  1.1473 +/**
  1.1474 + * Move the caret to the end of line.
  1.1475 + */
  1.1476 +function moveToLineEnd(aID, aCaretOffset)
  1.1477 +{
  1.1478 +  if (MAC) {
  1.1479 +    this.__proto__ = new synthKey(aID, "VK_RIGHT", { metaKey: true },
  1.1480 +                                  new caretMoveChecker(aCaretOffset, aID));
  1.1481 +  } else {
  1.1482 +    this.__proto__ = new synthEndKey(aID,
  1.1483 +                                     new caretMoveChecker(aCaretOffset, aID));
  1.1484 +  }
  1.1485 +
  1.1486 +  this.getID = function moveToLineEnd_getID()
  1.1487 +  {
  1.1488 +    return "move to line end in " + prettyName(aID);
  1.1489 +  }
  1.1490 +}
  1.1491 +
  1.1492 +/**
  1.1493 + * Move the caret to the end of previous line if any.
  1.1494 + */
  1.1495 +function moveToPrevLineEnd(aID, aCaretOffset)
  1.1496 +{
  1.1497 +  this.__proto__ = new synthAction(aID, new caretMoveChecker(aCaretOffset, aID));
  1.1498 +
  1.1499 +  this.invoke = function moveToPrevLineEnd_invoke()
  1.1500 +  {
  1.1501 +    synthesizeKey("VK_UP", { });
  1.1502 +
  1.1503 +    if (MAC)
  1.1504 +      synthesizeKey("VK_RIGHT", { metaKey: true });
  1.1505 +    else
  1.1506 +      synthesizeKey("VK_END", { });
  1.1507 +  }
  1.1508 +
  1.1509 +  this.getID = function moveToPrevLineEnd_getID()
  1.1510 +  {
  1.1511 +    return "move to previous line end in " + prettyName(aID);
  1.1512 +  }
  1.1513 +}
  1.1514 +
  1.1515 +/**
  1.1516 + * Move the caret to begining of the line.
  1.1517 + */
  1.1518 +function moveToLineStart(aID, aCaretOffset)
  1.1519 +{
  1.1520 +  if (MAC) {
  1.1521 +    this.__proto__ = new synthKey(aID, "VK_LEFT", { metaKey: true },
  1.1522 +                                  new caretMoveChecker(aCaretOffset, aID));
  1.1523 +  } else {
  1.1524 +    this.__proto__ = new synthHomeKey(aID,
  1.1525 +                                      new caretMoveChecker(aCaretOffset, aID));
  1.1526 +  }
  1.1527 +
  1.1528 +  this.getID = function moveToLineEnd_getID()
  1.1529 +  {
  1.1530 +    return "move to line start in " + prettyName(aID);
  1.1531 +  }
  1.1532 +}
  1.1533 +
  1.1534 +/**
  1.1535 + * Move the caret to begining of the text.
  1.1536 + */
  1.1537 +function moveToTextStart(aID)
  1.1538 +{
  1.1539 +  if (MAC) {
  1.1540 +    this.__proto__ = new synthKey(aID, "VK_UP", { metaKey: true },
  1.1541 +                                  new caretMoveChecker(0, aID));
  1.1542 +  } else {
  1.1543 +    this.__proto__ = new synthKey(aID, "VK_HOME", { ctrlKey: true },
  1.1544 +                                  new caretMoveChecker(0, aID));
  1.1545 +  }
  1.1546 +
  1.1547 +  this.getID = function moveToTextStart_getID()
  1.1548 +  {
  1.1549 +    return "move to text start in " + prettyName(aID);
  1.1550 +  }
  1.1551 +}
  1.1552 +
  1.1553 +/**
  1.1554 + * Move the caret in text accessible.
  1.1555 + */
  1.1556 +function moveCaretToDOMPoint(aID, aDOMPointNodeID, aDOMPointOffset,
  1.1557 +                             aExpectedOffset, aFocusTargetID,
  1.1558 +                             aCheckFunc)
  1.1559 +{
  1.1560 +  this.target = getAccessible(aID, [nsIAccessibleText]);
  1.1561 +  this.DOMPointNode = getNode(aDOMPointNodeID);
  1.1562 +  this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
  1.1563 +  this.focusNode = this.focus ? this.focus.DOMNode : null;
  1.1564 +
  1.1565 +  this.invoke = function moveCaretToDOMPoint_invoke()
  1.1566 +  {
  1.1567 +    if (this.focusNode)
  1.1568 +      this.focusNode.focus();
  1.1569 +
  1.1570 +    var selection = this.DOMPointNode.ownerDocument.defaultView.getSelection();
  1.1571 +    var selRange = selection.getRangeAt(0);
  1.1572 +    selRange.setStart(this.DOMPointNode, aDOMPointOffset);
  1.1573 +    selRange.collapse(true);
  1.1574 +
  1.1575 +    selection.removeRange(selRange);
  1.1576 +    selection.addRange(selRange);
  1.1577 +  }
  1.1578 +
  1.1579 +  this.getID = function moveCaretToDOMPoint_getID()
  1.1580 +  {
  1.1581 +   return "Set caret on " + prettyName(aID) + " at point: " +
  1.1582 +     prettyName(aDOMPointNodeID) + " node with offset " + aDOMPointOffset;
  1.1583 +  }
  1.1584 +
  1.1585 +  this.finalCheck = function moveCaretToDOMPoint_finalCheck()
  1.1586 +  {
  1.1587 +    if (aCheckFunc)
  1.1588 +      aCheckFunc.call();
  1.1589 +  }
  1.1590 +
  1.1591 +  this.eventSeq = [
  1.1592 +    new caretMoveChecker(aExpectedOffset, this.target)
  1.1593 +  ];
  1.1594 +
  1.1595 +  if (this.focus)
  1.1596 +    this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
  1.1597 +}
  1.1598 +
  1.1599 +/**
  1.1600 + * Set caret offset in text accessible.
  1.1601 + */
  1.1602 +function setCaretOffset(aID, aOffset, aFocusTargetID)
  1.1603 +{
  1.1604 +  this.target = getAccessible(aID, [nsIAccessibleText]);
  1.1605 +  this.offset = aOffset == -1 ? this.target.characterCount: aOffset;
  1.1606 +  this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
  1.1607 +
  1.1608 +  this.invoke = function setCaretOffset_invoke()
  1.1609 +  {
  1.1610 +    this.target.caretOffset = this.offset;
  1.1611 +  }
  1.1612 +
  1.1613 +  this.getID = function setCaretOffset_getID()
  1.1614 +  {
  1.1615 +   return "Set caretOffset on " + prettyName(aID) + " at " + this.offset;
  1.1616 +  }
  1.1617 +
  1.1618 +  this.eventSeq = [
  1.1619 +    new caretMoveChecker(this.offset, this.target)
  1.1620 +  ];
  1.1621 +
  1.1622 +  if (this.focus)
  1.1623 +    this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
  1.1624 +}
  1.1625 +
  1.1626 +
  1.1627 +////////////////////////////////////////////////////////////////////////////////
  1.1628 +// Event queue checkers
  1.1629 +
  1.1630 +/**
  1.1631 + * Common invoker checker (see eventSeq of eventQueue).
  1.1632 + */
  1.1633 +function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync)
  1.1634 +{
  1.1635 +  this.type = aEventType;
  1.1636 +  this.async = aIsAsync;
  1.1637 +
  1.1638 +  this.__defineGetter__("target", invokerChecker_targetGetter);
  1.1639 +  this.__defineSetter__("target", invokerChecker_targetSetter);
  1.1640 +
  1.1641 +  // implementation details
  1.1642 +  function invokerChecker_targetGetter()
  1.1643 +  {
  1.1644 +    if (typeof this.mTarget == "function")
  1.1645 +      return this.mTarget.call(null, this.mTargetFuncArg);
  1.1646 +    if (typeof this.mTarget == "string")
  1.1647 +      return getNode(this.mTarget);
  1.1648 +
  1.1649 +    return this.mTarget;
  1.1650 +  }
  1.1651 +
  1.1652 +  function invokerChecker_targetSetter(aValue)
  1.1653 +  {
  1.1654 +    this.mTarget = aValue;
  1.1655 +    return this.mTarget;
  1.1656 +  }
  1.1657 +
  1.1658 +  this.__defineGetter__("targetDescr", invokerChecker_targetDescrGetter);
  1.1659 +
  1.1660 +  function invokerChecker_targetDescrGetter()
  1.1661 +  {
  1.1662 +    if (typeof this.mTarget == "function")
  1.1663 +      return this.mTarget.name + ", arg: " + this.mTargetFuncArg;
  1.1664 +
  1.1665 +    return prettyName(this.mTarget);
  1.1666 +  }
  1.1667 +
  1.1668 +  this.mTarget = aTargetOrFunc;
  1.1669 +  this.mTargetFuncArg = aTargetFuncArg;
  1.1670 +}
  1.1671 +
  1.1672 +/**
  1.1673 + * Generic invoker checker for unexpected events.
  1.1674 + */
  1.1675 +function unexpectedInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
  1.1676 +{
  1.1677 +  this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
  1.1678 +                                      aTargetFuncArg, true);
  1.1679 +
  1.1680 +  this.unexpected = true;
  1.1681 +}
  1.1682 +
  1.1683 +/**
  1.1684 + * Common invoker checker for async events.
  1.1685 + */
  1.1686 +function asyncInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
  1.1687 +{
  1.1688 +  this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
  1.1689 +                                      aTargetFuncArg, true);
  1.1690 +}
  1.1691 +
  1.1692 +function focusChecker(aTargetOrFunc, aTargetFuncArg)
  1.1693 +{
  1.1694 +  this.__proto__ = new invokerChecker(EVENT_FOCUS, aTargetOrFunc,
  1.1695 +                                      aTargetFuncArg, false);
  1.1696 +
  1.1697 +  this.unique = true; // focus event must be unique for invoker action
  1.1698 +
  1.1699 +  this.check = function focusChecker_check(aEvent)
  1.1700 +  {
  1.1701 +    testStates(aEvent.accessible, STATE_FOCUSED);
  1.1702 +  }
  1.1703 +}
  1.1704 +
  1.1705 +function nofocusChecker(aID)
  1.1706 +{
  1.1707 +  this.__proto__ = new focusChecker(aID);
  1.1708 +  this.unexpected = true;
  1.1709 +}
  1.1710 +
  1.1711 +/**
  1.1712 + * Text inserted/removed events checker.
  1.1713 + * @param aFromUser  [in, optional] kNotFromUserInput or kFromUserInput
  1.1714 + */
  1.1715 +function textChangeChecker(aID, aStart, aEnd, aTextOrFunc, aIsInserted, aFromUser)
  1.1716 +{
  1.1717 +  this.target = getNode(aID);
  1.1718 +  this.type = aIsInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
  1.1719 +  this.startOffset = aStart;
  1.1720 +  this.endOffset = aEnd;
  1.1721 +  this.textOrFunc = aTextOrFunc;
  1.1722 +
  1.1723 +  this.check = function textChangeChecker_check(aEvent)
  1.1724 +  {
  1.1725 +    aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
  1.1726 +
  1.1727 +    var modifiedText = (typeof this.textOrFunc == "function") ?
  1.1728 +      this.textOrFunc() : this.textOrFunc;
  1.1729 +    var modifiedTextLen =
  1.1730 +      (this.endOffset == -1) ? modifiedText.length : aEnd - aStart;
  1.1731 +
  1.1732 +    is(aEvent.start, this.startOffset,
  1.1733 +       "Wrong start offset for " + prettyName(aID));
  1.1734 +    is(aEvent.length, modifiedTextLen, "Wrong length for " + prettyName(aID));
  1.1735 +    var changeInfo = (aIsInserted ? "inserted" : "removed");
  1.1736 +    is(aEvent.isInserted, aIsInserted,
  1.1737 +       "Text was " + changeInfo + " for " + prettyName(aID));
  1.1738 +    is(aEvent.modifiedText, modifiedText,
  1.1739 +       "Wrong " + changeInfo + " text for " + prettyName(aID));
  1.1740 +    if (typeof aFromUser != "undefined")
  1.1741 +      is(aEvent.isFromUserInput, aFromUser,
  1.1742 +         "wrong value of isFromUserInput() for " + prettyName(aID));
  1.1743 +  }
  1.1744 +}
  1.1745 +
  1.1746 +/**
  1.1747 + * Caret move events checker.
  1.1748 + */
  1.1749 +function caretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg)
  1.1750 +{
  1.1751 +  this.__proto__ = new invokerChecker(EVENT_TEXT_CARET_MOVED,
  1.1752 +                                      aTargetOrFunc, aTargetFuncArg);
  1.1753 +
  1.1754 +  this.check = function caretMoveChecker_check(aEvent)
  1.1755 +  {
  1.1756 +    is(aEvent.QueryInterface(nsIAccessibleCaretMoveEvent).caretOffset,
  1.1757 +       aCaretOffset,
  1.1758 +       "Wrong caret offset for " + prettyName(aEvent.accessible));
  1.1759 +  }
  1.1760 +}
  1.1761 +
  1.1762 +/**
  1.1763 + * Text selection change checker.
  1.1764 + */
  1.1765 +function textSelectionChecker(aID, aStartOffset, aEndOffset)
  1.1766 +{
  1.1767 +  this.__proto__ = new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID);
  1.1768 +
  1.1769 +  this.check = function textSelectionChecker_check(aEvent)
  1.1770 +  {
  1.1771 +    if (aStartOffset == aEndOffset) {
  1.1772 +      is(getAccessible(aID, [nsIAccessibleText]).caretOffset, aStartOffset,
  1.1773 +         "Wrong collapsed selection!");
  1.1774 +    } else {
  1.1775 +      testTextGetSelection(aID, aStartOffset, aEndOffset, 0);
  1.1776 +    }
  1.1777 +  }
  1.1778 +}
  1.1779 +
  1.1780 +/**
  1.1781 + * State change checker.
  1.1782 + */
  1.1783 +function stateChangeChecker(aState, aIsExtraState, aIsEnabled,
  1.1784 +                            aTargetOrFunc, aTargetFuncArg, aIsAsync,
  1.1785 +                            aSkipCurrentStateCheck)
  1.1786 +{
  1.1787 +  this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
  1.1788 +                                      aTargetFuncArg, aIsAsync);
  1.1789 +
  1.1790 +  this.check = function stateChangeChecker_check(aEvent)
  1.1791 +  {
  1.1792 +    var event = null;
  1.1793 +    try {
  1.1794 +      var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
  1.1795 +    } catch (e) {
  1.1796 +      ok(false, "State change event was expected");
  1.1797 +    }
  1.1798 +
  1.1799 +    if (!event)
  1.1800 +      return;
  1.1801 +
  1.1802 +    is(event.isExtraState, aIsExtraState,
  1.1803 +       "Wrong extra state bit of the statechange event.");
  1.1804 +    isState(event.state, aState, aIsExtraState,
  1.1805 +            "Wrong state of the statechange event.");
  1.1806 +    is(event.isEnabled, aIsEnabled,
  1.1807 +      "Wrong state of statechange event state");
  1.1808 +
  1.1809 +    if (aSkipCurrentStateCheck) {
  1.1810 +      todo(false, "State checking was skipped!");
  1.1811 +      return;
  1.1812 +    }
  1.1813 +
  1.1814 +    var state = aIsEnabled ? (aIsExtraState ? 0 : aState) : 0;
  1.1815 +    var extraState = aIsEnabled ? (aIsExtraState ? aState : 0) : 0;
  1.1816 +    var unxpdState = aIsEnabled ? 0 : (aIsExtraState ? 0 : aState);
  1.1817 +    var unxpdExtraState = aIsEnabled ? 0 : (aIsExtraState ? aState : 0);
  1.1818 +    testStates(event.accessible, state, extraState, unxpdState, unxpdExtraState);
  1.1819 +  }
  1.1820 +
  1.1821 +  this.match = function stateChangeChecker_match(aEvent)
  1.1822 +  {
  1.1823 +    if (aEvent instanceof nsIAccessibleStateChangeEvent) {
  1.1824 +      var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
  1.1825 +      return (aEvent.accessible == getAccessible(this.target)) &&
  1.1826 +        (scEvent.state == aState);
  1.1827 +    }
  1.1828 +    return false;
  1.1829 +  }
  1.1830 +}
  1.1831 +
  1.1832 +function asyncStateChangeChecker(aState, aIsExtraState, aIsEnabled,
  1.1833 +                                 aTargetOrFunc, aTargetFuncArg)
  1.1834 +{
  1.1835 +  this.__proto__ = new stateChangeChecker(aState, aIsExtraState, aIsEnabled,
  1.1836 +                                          aTargetOrFunc, aTargetFuncArg, true);
  1.1837 +}
  1.1838 +
  1.1839 +/**
  1.1840 + * Expanded state change checker.
  1.1841 + */
  1.1842 +function expandedStateChecker(aIsEnabled, aTargetOrFunc, aTargetFuncArg)
  1.1843 +{
  1.1844 +  this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
  1.1845 +                                      aTargetFuncArg);
  1.1846 +
  1.1847 +  this.check = function expandedStateChecker_check(aEvent)
  1.1848 +  {
  1.1849 +    var event = null;
  1.1850 +    try {
  1.1851 +      var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
  1.1852 +    } catch (e) {
  1.1853 +      ok(false, "State change event was expected");
  1.1854 +    }
  1.1855 +
  1.1856 +    if (!event)
  1.1857 +      return;
  1.1858 +
  1.1859 +    is(event.state, STATE_EXPANDED, "Wrong state of the statechange event.");
  1.1860 +    is(event.isExtraState, false,
  1.1861 +       "Wrong extra state bit of the statechange event.");
  1.1862 +    is(event.isEnabled, aIsEnabled,
  1.1863 +      "Wrong state of statechange event state");
  1.1864 +
  1.1865 +    testStates(event.accessible,
  1.1866 +               (aIsEnabled ? STATE_EXPANDED : STATE_COLLAPSED));
  1.1867 +  }
  1.1868 +}
  1.1869 +
  1.1870 +////////////////////////////////////////////////////////////////////////////////
  1.1871 +// Event sequances (array of predefined checkers)
  1.1872 +
  1.1873 +/**
  1.1874 + * Event seq for single selection change.
  1.1875 + */
  1.1876 +function selChangeSeq(aUnselectedID, aSelectedID)
  1.1877 +{
  1.1878 +  if (!aUnselectedID) {
  1.1879 +    return [
  1.1880 +      new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
  1.1881 +      new invokerChecker(EVENT_SELECTION, aSelectedID)
  1.1882 +    ];
  1.1883 +  }
  1.1884 +
  1.1885 +  // Return two possible scenarios: depending on widget type when selection is
  1.1886 +  // moved the the order of items that get selected and unselected may vary. 
  1.1887 +  return [
  1.1888 +    [
  1.1889 +      new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
  1.1890 +      new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
  1.1891 +      new invokerChecker(EVENT_SELECTION, aSelectedID)
  1.1892 +    ],
  1.1893 +    [
  1.1894 +      new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
  1.1895 +      new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
  1.1896 +      new invokerChecker(EVENT_SELECTION, aSelectedID)
  1.1897 +    ]
  1.1898 +  ];
  1.1899 +}
  1.1900 +
  1.1901 +/**
  1.1902 + * Event seq for item removed form the selection.
  1.1903 + */
  1.1904 +function selRemoveSeq(aUnselectedID)
  1.1905 +{
  1.1906 +  return [
  1.1907 +    new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
  1.1908 +    new invokerChecker(EVENT_SELECTION_REMOVE, aUnselectedID)
  1.1909 +  ];
  1.1910 +}
  1.1911 +
  1.1912 +/**
  1.1913 + * Event seq for item added to the selection.
  1.1914 + */
  1.1915 +function selAddSeq(aSelectedID)
  1.1916 +{
  1.1917 +  return [
  1.1918 +    new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
  1.1919 +    new invokerChecker(EVENT_SELECTION_ADD, aSelectedID)
  1.1920 +  ];
  1.1921 +}
  1.1922 +
  1.1923 +////////////////////////////////////////////////////////////////////////////////
  1.1924 +// Private implementation details.
  1.1925 +////////////////////////////////////////////////////////////////////////////////
  1.1926 +
  1.1927 +
  1.1928 +////////////////////////////////////////////////////////////////////////////////
  1.1929 +// General
  1.1930 +
  1.1931 +var gA11yEventListeners = {};
  1.1932 +var gA11yEventApplicantsCount = 0;
  1.1933 +
  1.1934 +var gA11yEventObserver =
  1.1935 +{
  1.1936 +  observe: function observe(aSubject, aTopic, aData)
  1.1937 +  {
  1.1938 +    if (aTopic != "accessible-event")
  1.1939 +      return;
  1.1940 +
  1.1941 +    var event;
  1.1942 +    try {
  1.1943 +      event = aSubject.QueryInterface(nsIAccessibleEvent);
  1.1944 +    } catch (ex) {
  1.1945 +      // After a test is aborted (i.e. timed out by the harness), this exception is soon triggered.
  1.1946 +      // Remove the leftover observer, otherwise it "leaks" to all the following tests.
  1.1947 +      Services.obs.removeObserver(this, "accessible-event");
  1.1948 +      // Forward the exception, with added explanation.
  1.1949 +      throw "[accessible/events.js, gA11yEventObserver.observe] This is expected if a previous test has been aborted... Initial exception was: [ " + ex + " ]";
  1.1950 +    }
  1.1951 +    var listenersArray = gA11yEventListeners[event.eventType];
  1.1952 +
  1.1953 +    var eventFromDumpArea = false;
  1.1954 +    if (gLogger.isEnabled()) { // debug stuff
  1.1955 +      eventFromDumpArea = true;
  1.1956 +
  1.1957 +      var target = event.DOMNode;
  1.1958 +      var dumpElm = gA11yEventDumpID ?
  1.1959 +        document.getElementById(gA11yEventDumpID) : null;
  1.1960 +
  1.1961 +      if (dumpElm) {
  1.1962 +        var parent = target;
  1.1963 +        while (parent && parent != dumpElm)
  1.1964 +          parent = parent.parentNode;
  1.1965 +      }
  1.1966 +
  1.1967 +      if (!dumpElm || parent != dumpElm) {
  1.1968 +        var type = eventTypeToString(event.eventType);
  1.1969 +        var info = "Event type: " + type;
  1.1970 +
  1.1971 +        if (event instanceof nsIAccessibleStateChangeEvent) {
  1.1972 +          var stateStr = statesToString(event.isExtraState ? 0 : event.state,
  1.1973 +                                        event.isExtraState ? event.state : 0);
  1.1974 +          info += ", state: " + stateStr + ", is enabled: " + event.isEnabled;
  1.1975 +
  1.1976 +        } else if (event instanceof nsIAccessibleTextChangeEvent) {
  1.1977 +          info += ", start: " + event.start + ", length: " + event.length +
  1.1978 +            ", " + (event.isInserted ? "inserted" : "removed") +
  1.1979 +            " text: " + event.modifiedText;
  1.1980 +        }
  1.1981 +
  1.1982 +        info += ". Target: " + prettyName(event.accessible);
  1.1983 +
  1.1984 +        if (listenersArray)
  1.1985 +          info += ". Listeners count: " + listenersArray.length;
  1.1986 +
  1.1987 +        if (gLogger.hasFeature("parentchain:" + type)) {
  1.1988 +          info += "\nParent chain:\n";
  1.1989 +          var acc = event.accessible;
  1.1990 +          while (acc) {
  1.1991 +            info += "  " + prettyName(acc) + "\n";
  1.1992 +            acc = acc.parent;
  1.1993 +          }
  1.1994 +        }
  1.1995 +
  1.1996 +        eventFromDumpArea = false;
  1.1997 +        gLogger.log(info);
  1.1998 +      }
  1.1999 +    }
  1.2000 +
  1.2001 +    // Do not notify listeners if event is result of event log changes.
  1.2002 +    if (!listenersArray || eventFromDumpArea)
  1.2003 +      return;
  1.2004 +
  1.2005 +    for (var index = 0; index < listenersArray.length; index++)
  1.2006 +      listenersArray[index].handleEvent(event);
  1.2007 +  }
  1.2008 +};
  1.2009 +
  1.2010 +function listenA11yEvents(aStartToListen)
  1.2011 +{
  1.2012 +  if (aStartToListen) {
  1.2013 +    // Add observer when adding the first applicant only.
  1.2014 +    if (!(gA11yEventApplicantsCount++))
  1.2015 +      Services.obs.addObserver(gA11yEventObserver, "accessible-event", false);
  1.2016 +  } else {
  1.2017 +    // Remove observer when there are no more applicants only.
  1.2018 +    // '< 0' case should not happen, but just in case: removeObserver() will throw.
  1.2019 +    if (--gA11yEventApplicantsCount <= 0)
  1.2020 +      Services.obs.removeObserver(gA11yEventObserver, "accessible-event");
  1.2021 +  }
  1.2022 +}
  1.2023 +
  1.2024 +function addA11yEventListener(aEventType, aEventHandler)
  1.2025 +{
  1.2026 +  if (!(aEventType in gA11yEventListeners))
  1.2027 +    gA11yEventListeners[aEventType] = new Array();
  1.2028 +
  1.2029 +  var listenersArray = gA11yEventListeners[aEventType];
  1.2030 +  var index = listenersArray.indexOf(aEventHandler);
  1.2031 +  if (index == -1)
  1.2032 +    listenersArray.push(aEventHandler);
  1.2033 +}
  1.2034 +
  1.2035 +function removeA11yEventListener(aEventType, aEventHandler)
  1.2036 +{
  1.2037 +  var listenersArray = gA11yEventListeners[aEventType];
  1.2038 +  if (!listenersArray)
  1.2039 +    return false;
  1.2040 +
  1.2041 +  var index = listenersArray.indexOf(aEventHandler);
  1.2042 +  if (index == -1)
  1.2043 +    return false;
  1.2044 +
  1.2045 +  listenersArray.splice(index, 1);
  1.2046 +  
  1.2047 +  if (!listenersArray.length) {
  1.2048 +    gA11yEventListeners[aEventType] = null;
  1.2049 +    delete gA11yEventListeners[aEventType];
  1.2050 +  }
  1.2051 +
  1.2052 +  return true;
  1.2053 +}
  1.2054 +
  1.2055 +/**
  1.2056 + * Used to dump debug information.
  1.2057 + */
  1.2058 +var gLogger =
  1.2059 +{
  1.2060 +  /**
  1.2061 +   * Return true if dump is enabled.
  1.2062 +   */
  1.2063 +  isEnabled: function debugOutput_isEnabled()
  1.2064 +  {
  1.2065 +    return gA11yEventDumpID || gA11yEventDumpToConsole ||
  1.2066 +      gA11yEventDumpToAppConsole;
  1.2067 +  },
  1.2068 +
  1.2069 +  /**
  1.2070 +   * Dump information into DOM and console if applicable.
  1.2071 +   */
  1.2072 +  log: function logger_log(aMsg)
  1.2073 +  {
  1.2074 +    this.logToConsole(aMsg);
  1.2075 +    this.logToAppConsole(aMsg);
  1.2076 +    this.logToDOM(aMsg);
  1.2077 +  },
  1.2078 +
  1.2079 +  /**
  1.2080 +   * Log message to DOM.
  1.2081 +   *
  1.2082 +   * @param aMsg          [in] the primary message
  1.2083 +   * @param aHasIndent    [in, optional] if specified the message has an indent
  1.2084 +   * @param aPreEmphText  [in, optional] the text is colored and appended prior
  1.2085 +   *                        primary message
  1.2086 +   */
  1.2087 +  logToDOM: function logger_logToDOM(aMsg, aHasIndent, aPreEmphText)
  1.2088 +  {
  1.2089 +    if (gA11yEventDumpID == "")
  1.2090 +      return;
  1.2091 +
  1.2092 +    var dumpElm = document.getElementById(gA11yEventDumpID);
  1.2093 +    if (!dumpElm) {
  1.2094 +      ok(false,
  1.2095 +         "No dump element '" + gA11yEventDumpID + "' within the document!");
  1.2096 +      return;
  1.2097 +    }
  1.2098 +
  1.2099 +    var containerTagName = document instanceof nsIDOMHTMLDocument ?
  1.2100 +      "div" : "description";
  1.2101 +
  1.2102 +    var container = document.createElement(containerTagName);
  1.2103 +    if (aHasIndent)
  1.2104 +      container.setAttribute("style", "padding-left: 10px;");
  1.2105 +
  1.2106 +    if (aPreEmphText) {
  1.2107 +      var inlineTagName = document instanceof nsIDOMHTMLDocument ?
  1.2108 +        "span" : "description";
  1.2109 +      var emphElm = document.createElement(inlineTagName);
  1.2110 +      emphElm.setAttribute("style", "color: blue;");
  1.2111 +      emphElm.textContent = aPreEmphText;
  1.2112 +
  1.2113 +      container.appendChild(emphElm);
  1.2114 +    }
  1.2115 +
  1.2116 +    var textNode = document.createTextNode(aMsg);
  1.2117 +    container.appendChild(textNode);
  1.2118 +
  1.2119 +    dumpElm.appendChild(container);
  1.2120 +  },
  1.2121 +
  1.2122 +  /**
  1.2123 +   * Log message to console.
  1.2124 +   */
  1.2125 +  logToConsole: function logger_logToConsole(aMsg)
  1.2126 +  {
  1.2127 +    if (gA11yEventDumpToConsole)
  1.2128 +      dump("\n" + aMsg + "\n");
  1.2129 +  },
  1.2130 +
  1.2131 +  /**
  1.2132 +   * Log message to error console.
  1.2133 +   */
  1.2134 +  logToAppConsole: function logger_logToAppConsole(aMsg)
  1.2135 +  {
  1.2136 +    if (gA11yEventDumpToAppConsole)
  1.2137 +      Services.console.logStringMessage("events: " + aMsg);
  1.2138 +  },
  1.2139 +
  1.2140 +  /**
  1.2141 +   * Return true if logging feature is enabled.
  1.2142 +   */
  1.2143 +  hasFeature: function logger_hasFeature(aFeature)
  1.2144 +  {
  1.2145 +    var startIdx = gA11yEventDumpFeature.indexOf(aFeature);
  1.2146 +    if (startIdx == - 1)
  1.2147 +      return false;
  1.2148 +
  1.2149 +    var endIdx = startIdx + aFeature.length;
  1.2150 +    return endIdx == gA11yEventDumpFeature.length ||
  1.2151 +      gA11yEventDumpFeature[endIdx] == ";";
  1.2152 +  }
  1.2153 +};
  1.2154 +
  1.2155 +
  1.2156 +////////////////////////////////////////////////////////////////////////////////
  1.2157 +// Sequence
  1.2158 +
  1.2159 +/**
  1.2160 + * Base class of sequence item.
  1.2161 + */
  1.2162 +function sequenceItem(aProcessor, aEventType, aTarget, aItemID)
  1.2163 +{
  1.2164 +  // private
  1.2165 +  
  1.2166 +  this.startProcess = function sequenceItem_startProcess()
  1.2167 +  {
  1.2168 +    this.queue.invoke();
  1.2169 +  }
  1.2170 +  
  1.2171 +  var item = this;
  1.2172 +  
  1.2173 +  this.queue = new eventQueue();
  1.2174 +  this.queue.onFinish = function()
  1.2175 +  {
  1.2176 +    aProcessor.onProcessed();
  1.2177 +    return DO_NOT_FINISH_TEST;
  1.2178 +  }
  1.2179 +  
  1.2180 +  var invoker = {
  1.2181 +    invoke: function invoker_invoke() {
  1.2182 +      return aProcessor.process();
  1.2183 +    },
  1.2184 +    getID: function invoker_getID()
  1.2185 +    {
  1.2186 +      return aItemID;
  1.2187 +    },
  1.2188 +    eventSeq: [ new invokerChecker(aEventType, aTarget) ]
  1.2189 +  };
  1.2190 +  
  1.2191 +  this.queue.push(invoker);
  1.2192 +}
  1.2193 +
  1.2194 +////////////////////////////////////////////////////////////////////////////////
  1.2195 +// Event queue invokers
  1.2196 +
  1.2197 +/**
  1.2198 + * Invoker base class for prepare an action.
  1.2199 + */
  1.2200 +function synthAction(aNodeOrID, aEventsObj)
  1.2201 +{
  1.2202 +  this.DOMNode = getNode(aNodeOrID);
  1.2203 +
  1.2204 +  if (aEventsObj) {
  1.2205 +    var scenarios = null;
  1.2206 +    if (aEventsObj instanceof Array) {
  1.2207 +      if (aEventsObj[0] instanceof Array)
  1.2208 +        scenarios = aEventsObj; // scenarios
  1.2209 +      else
  1.2210 +        scenarios = [ aEventsObj ]; // event sequance
  1.2211 +    } else {
  1.2212 +      scenarios = [ [ aEventsObj ] ]; // a single checker object
  1.2213 +    }
  1.2214 +
  1.2215 +    for (var i = 0; i < scenarios.length; i++)
  1.2216 +      defineScenario(this, scenarios[i]);
  1.2217 +  }
  1.2218 +
  1.2219 +  this.getID = function synthAction_getID()
  1.2220 +    { return prettyName(aNodeOrID) + " action"; }
  1.2221 +}

mercurial