Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  events.js   Sprache: JAVA

 
/* import-globals-from common.js */
/* import-globals-from states.js */
/* import-globals-from text.js */

// XXX Bug 1425371 - enable no-redeclare and fix the issues with the tests.
/* eslint-disable no-redeclare */

// //////////////////////////////////////////////////////////////////////////////
// Constants

const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT;
const EVENT_ANNOUNCEMENT = nsIAccessibleEvent.EVENT_ANNOUNCEMENT;
const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
const EVENT_DOCUMENT_LOAD_COMPLETE =
  nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD;
const EVENT_DOCUMENT_LOAD_STOPPED =
  nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_STOPPED;
const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
const EVENT_MENU_START = nsIAccessibleEvent.EVENT_MENU_START;
const EVENT_MENU_END = nsIAccessibleEvent.EVENT_MENU_END;
const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START;
const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END;
const EVENT_OBJECT_ATTRIBUTE_CHANGED =
  nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED;
const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START;
const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION;
const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD;
const EVENT_SELECTION_REMOVE = nsIAccessibleEvent.EVENT_SELECTION_REMOVE;
const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
const EVENT_TEXT_ATTRIBUTE_CHANGED =
  nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED;
const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
const EVENT_TEXT_SELECTION_CHANGED =
  nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED;
const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE;
const EVENT_VIRTUALCURSOR_CHANGED =
  nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED;

const kNotFromUserInput = 0;
const kFromUserInput = 1;

// //////////////////////////////////////////////////////////////////////////////
// General

/**
 * Set up this variable to dump events into DOM.
 */

var gA11yEventDumpID = "";

/**
 * Set up this variable to dump event processing into console.
 */

var gA11yEventDumpToConsole = false;

/**
 * Set up this variable to dump event processing into error console.
 */

var gA11yEventDumpToAppConsole = false;

/**
 * Semicolon separated set of logging features.
 */

var gA11yEventDumpFeature = "";

/**
 * Function to detect HTML elements when given a node.
 */

function isHTMLElement(aNode) {
  return (
    aNode.nodeType == aNode.ELEMENT_NODE &&
    aNode.namespaceURI == "http://www.w3.org/1999/xhtml"
  );
}

function isXULElement(aNode) {
  return (
    aNode.nodeType == aNode.ELEMENT_NODE &&
    aNode.namespaceURI ==
      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  );
}

/**
 * Executes the function when requested event is handled.
 *
 * @param aEventType  [in] event type
 * @param aTarget     [in] event target
 * @param aFunc       [in] function to call when event is handled
 * @param aContext    [in, optional] object in which context the function is
 *                    called
 * @param aArg1       [in, optional] argument passed into the function
 * @param aArg2       [in, optional] argument passed into the function
 */

function waitForEvent(
  aEventType,
  aTargetOrFunc,
  aFunc,
  aContext,
  aArg1,
  aArg2
) {
  var handler = {
    handleEvent: function handleEvent(aEvent) {
      var target = aTargetOrFunc;
      if (typeof aTargetOrFunc == "function") {
        target = aTargetOrFunc.call();
      }

      if (target) {
        if (target instanceof nsIAccessible && target != aEvent.accessible) {
          return;
        }

        if (Node.isInstance(target) && target != aEvent.DOMNode) {
          return;
        }
      }

      unregisterA11yEventListener(aEventType, this);

      window.setTimeout(function () {
        aFunc.call(aContext, aArg1, aArg2);
      }, 0);
    },
  };

  registerA11yEventListener(aEventType, handler);
}

/**
 * Generate mouse move over image map what creates image map accessible (async).
 * See waitForImageMap() function.
 */

function waveOverImageMap(aImageMapID) {
  var imageMapNode = getNode(aImageMapID);
  synthesizeMouse(
    imageMapNode,
    10,
    10,
    { type: "mousemove" },
    imageMapNode.ownerGlobal
  );
}

/**
 * Call the given function when the tree of the given image map is built.
 */

function waitForImageMap(aImageMapID, aTestFunc) {
  waveOverImageMap(aImageMapID);

  var imageMapAcc = getAccessible(aImageMapID);
  if (imageMapAcc.firstChild) {
    aTestFunc();
    return;
  }

  waitForEvent(EVENT_REORDER, imageMapAcc, aTestFunc);
}

/**
 * Register accessibility event listener.
 *
 * @param aEventType     the accessible event type (see nsIAccessibleEvent for
 *                       available constants).
 * @param aEventHandler  event listener object, when accessible event of the
 *                       given type is handled then 'handleEvent' method of
 *                       this object is invoked with nsIAccessibleEvent object
 *                       as the first argument.
 */

function registerA11yEventListener(aEventType, aEventHandler) {
  listenA11yEvents(true);
  addA11yEventListener(aEventType, aEventHandler);
}

/**
 * Unregister accessibility event listener. Must be called for every registered
 * event listener (see registerA11yEventListener() function) when the listener
 * is not needed.
 */

function unregisterA11yEventListener(aEventType, aEventHandler) {
  removeA11yEventListener(aEventType, aEventHandler);
  listenA11yEvents(false);
}

// //////////////////////////////////////////////////////////////////////////////
// Event queue

/**
 * Return value of invoke method of invoker object. Indicates invoker was unable
 * to prepare action.
 */

const INVOKER_ACTION_FAILED = 1;

/**
 * Return value of eventQueue.onFinish. Indicates eventQueue should not finish
 * tests.
 */

const DO_NOT_FINISH_TEST = 1;

/**
 * Creates event queue for the given event type. The queue consists of invoker
 * objects, each of them generates the event of the event type. When queue is
 * started then every invoker object is asked to generate event after timeout.
 * When event is caught then current invoker object is asked to check whether
 * event was handled correctly.
 *
 * Invoker interface is:
 *
 *   var invoker = {
 *     // Generates accessible event or event sequence. If returns
 *     // INVOKER_ACTION_FAILED constant then stop tests.
 *     invoke: function(){},
 *
 *     // [optional] Invoker's check of handled event for correctness.
 *     check: function(aEvent){},
 *
 *     // [optional] Invoker's check before the next invoker is proceeded.
 *     finalCheck: function(aEvent){},
 *
 *     // [optional] Is called when event of any registered type is handled.
 *     debugCheck: function(aEvent){},
 *
 *     // [ignored if 'eventSeq' is defined] DOM node event is generated for
 *     // (used in the case when invoker expects single event).
 *     DOMNode getter: function() {},
 *
 *     // [optional] if true then event sequences are ignored (no failure if
 *     // sequences are empty). Use you need to invoke an action, do some check
 *     // after timeout and proceed a next invoker.
 *     noEventsOnAction getter: function() {},
 *
 *     // Array of checker objects defining expected events on invoker's action.
 *     //
 *     // Checker object interface:
 *     //
 *     // var checker = {
 *     //   * DOM or a11y event type. *
 *     //   type getter: function() {},
 *     //
 *     //   * DOM node or accessible. *
 *     //   target getter: function() {},
 *     //
 *     //   * DOM event phase (false - bubbling). *
 *     //   phase getter: function() {},
 *     //
 *     //   * Callback, called to match handled event. *
 *     //   match : function(aEvent) {},
 *     //
 *     //   * Callback, called when event is handled
 *     //   check: function(aEvent) {},
 *     //
 *     //   * Checker ID *
 *     //   getID: function() {},
 *     //
 *     //   * Event that don't have predefined order relative other events. *
 *     //   async getter: function() {},
 *     //
 *     //   * Event that is not expected. *
 *     //   unexpected getter: function() {},
 *     //
 *     //   * No other event of the same type is not allowed. *
 *     //   unique getter: function() {}
 *     // };
 *     eventSeq getter() {},
 *
 *     // Array of checker objects defining unexpected events on invoker's
 *     // action.
 *     unexpectedEventSeq getter() {},
 *
 *     // The ID of invoker.
 *     getID: function(){} // returns invoker ID
 *   };
 *
 *   // Used to add a possible scenario of expected/unexpected events on
 *   // invoker's action.
 *  defineScenario(aInvokerObj, aEventSeq, aUnexpectedEventSeq)
 *
 *
 * @param  aEventType  [in, optional] the default event type (isn't used if
 *                      invoker defines eventSeq property).
 */

function eventQueue(aEventType) {
  // public

  /**
   * Add invoker object into queue.
   */

  this.push = function eventQueue_push(aEventInvoker) {
    this.mInvokers.push(aEventInvoker);
  };

  /**
   * Start the queue processing.
   */

  this.invoke = function eventQueue_invoke() {
    listenA11yEvents(true);

    // XXX: Intermittent test_events_caretmove.html fails withouth timeout,
    // see bug 474952.
    this.processNextInvokerInTimeout(true);
  };

  /**
   * This function is called when all events in the queue were handled.
   * Override it if you need to be notified of this.
   */

  this.onFinish = function eventQueue_finish() {};

  // private

  /**
   * Process next invoker.
   */

  // eslint-disable-next-line complexity
  this.processNextInvoker = function eventQueue_processNextInvoker() {
    // Some scenario was matched, we wait on next invoker processing.
    if (this.mNextInvokerStatus == kInvokerCanceled) {
      this.setInvokerStatus(
        kInvokerNotScheduled,
        "scenario was matched, wait for next invoker activation"
      );
      return;
    }

    this.setInvokerStatus(
      kInvokerNotScheduled,
      "the next invoker is processed now"
    );

    // Finish processing of the current invoker if any.
    var testFailed = false;

    var invoker = this.getInvoker();
    if (invoker) {
      if ("finalCheck" in invoker) {
        invoker.finalCheck();
      }

      if (this.mScenarios && this.mScenarios.length) {
        var matchIdx = -1;
        for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
          var eventSeq = this.mScenarios[scnIdx];
          if (!this.areExpectedEventsLeft(eventSeq)) {
            for (var idx = 0; idx < eventSeq.length; idx++) {
              var checker = eventSeq[idx];
              if (
                (checker.unexpected && checker.wasCaught) ||
                (!checker.unexpected && checker.wasCaught != 1)
              ) {
                break;
              }
            }

            // Ok, we have matched scenario. Report it was completed ok. In
            // case of empty scenario guess it was matched but if later we
            // find out that non empty scenario was matched then it will be
            // a final match.
            if (idx == eventSeq.length) {
              if (
                matchIdx != -1 &&
                !!eventSeq.length &&
                this.mScenarios[matchIdx].length
              ) {
                ok(
                  false,
                  "We have a matched scenario at index " +
                    matchIdx +
                    " already."
                );
              }

              if (matchIdx == -1 || eventSeq.length) {
                matchIdx = scnIdx;
              }

              // Report everything is ok.
              for (var idx = 0; idx < eventSeq.length; idx++) {
                var checker = eventSeq[idx];

                var typeStr = eventQueue.getEventTypeAsString(checker);
                var msg =
                  "Test with ID = '" + this.getEventID(checker) + "' succeed. ";

                if (checker.unexpected) {
                  ok(true, msg + `There's no unexpected '${typeStr}' event.`);
                } else if (checker.todo) {
                  todo(false, `Todo event '${typeStr}' was caught`);
                } else {
                  ok(true, `${msg} Event '${typeStr}' was handled.`);
                }
              }
            }
          }
        }

        // We don't have completely matched scenario. Report each failure/success
        // for every scenario.
        if (matchIdx == -1) {
          testFailed = true;
          for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
            var eventSeq = this.mScenarios[scnIdx];
            for (var idx = 0; idx < eventSeq.length; idx++) {
              var checker = eventSeq[idx];

              var typeStr = eventQueue.getEventTypeAsString(checker);
              var msg =
                "Scenario #" +
                scnIdx +
                " of test with ID = '" +
                this.getEventID(checker) +
                "' failed. ";

              if (checker.wasCaught > 1) {
                ok(false, msg + "Dupe " + typeStr + " event.");
              }

              if (checker.unexpected) {
                if (checker.wasCaught) {
                  ok(false, msg + "There's unexpected " + typeStr + " event.");
                }
              } else if (!checker.wasCaught) {
                var rf = checker.todo ? todo : ok;
                rf(false, `${msg} '${typeStr} event is missed.`);
              }
            }
          }
        }
      }
    }

    this.clearEventHandler();

    // Check if need to stop the test.
    if (testFailed || this.mIndex == this.mInvokers.length - 1) {
      listenA11yEvents(false);

      var res = this.onFinish();
      if (res != DO_NOT_FINISH_TEST) {
        SimpleTest.executeSoon(SimpleTest.finish);
      }

      return;
    }

    // Start processing of next invoker.
    invoker = this.getNextInvoker();

    // Set up event listeners. Process a next invoker if no events were added.
    if (!this.setEventHandler(invoker)) {
      this.processNextInvoker();
      return;
    }

    if (gLogger.isEnabled()) {
      gLogger.logToConsole("Event queue: \n invoke: " + invoker.getID());
      gLogger.logToDOM("EQ: invoke: " + invoker.getID(), true);
    }

    var infoText = "Invoke the '" + invoker.getID() + "' test { ";
    var scnCount = this.mScenarios ? this.mScenarios.length : 0;
    for (var scnIdx = 0; scnIdx < scnCount; scnIdx++) {
      infoText += "scenario #" + scnIdx + ": ";
      var eventSeq = this.mScenarios[scnIdx];
      for (var idx = 0; idx < eventSeq.length; idx++) {
        infoText += eventSeq[idx].unexpected
          ? "un"
          : "" +
            "expected '" +
            eventQueue.getEventTypeAsString(eventSeq[idx]) +
            "' event; ";
      }
    }
    infoText += " }";
    info(infoText);

    if (invoker.invoke() == INVOKER_ACTION_FAILED) {
      // Invoker failed to prepare action, fail and finish tests.
      this.processNextInvoker();
      return;
    }

    if (this.hasUnexpectedEventsScenario()) {
      this.processNextInvokerInTimeout(true);
    }
  };

  this.processNextInvokerInTimeout =
    function eventQueue_processNextInvokerInTimeout(aUncondProcess) {
      this.setInvokerStatus(kInvokerPending, "Process next invoker in timeout");

      // No need to wait extra timeout when a) we know we don't need to do that
      // and b) there's no any single unexpected event.
      if (!aUncondProcess && this.areAllEventsExpected()) {
        // We need delay to avoid events coalesce from different invokers.
        var queue = this;
        SimpleTest.executeSoon(function () {
          queue.processNextInvoker();
        });
        return;
      }

      // Check in timeout invoker didn't fire registered events.
      window.setTimeout(
        function (aQueue) {
          aQueue.processNextInvoker();
        },
        300,
        this
      );
    };

  /**
   * Handle events for the current invoker.
   */

  // eslint-disable-next-line complexity
  this.handleEvent = function eventQueue_handleEvent(aEvent) {
    var invoker = this.getInvoker();
    if (!invoker) {
      // skip events before test was started
      return;
    }

    if (!this.mScenarios) {
      // Bad invoker object, error will be reported before processing of next
      // invoker in the queue.
      this.processNextInvoker();
      return;
    }

    if ("debugCheck" in invoker) {
      invoker.debugCheck(aEvent);
    }

    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
      var eventSeq = this.mScenarios[scnIdx];
      for (var idx = 0; idx < eventSeq.length; idx++) {
        var checker = eventSeq[idx];

        // Search through handled expected events to report error if one of them
        // is handled for a second time.
        if (
          !checker.unexpected &&
          checker.wasCaught > 0 &&
          eventQueue.isSameEvent(checker, aEvent)
        ) {
          checker.wasCaught++;
          continue;
        }

        // Search through unexpected events, any match results in error report
        // after this invoker processing (in case of matched scenario only).
        if (checker.unexpected && eventQueue.compareEvents(checker, aEvent)) {
          checker.wasCaught++;
          continue;
        }

        // Report an error if we handled not expected event of unique type
        // (i.e. event types are matched, targets differs).
        if (
          !checker.unexpected &&
          checker.unique &&
          eventQueue.compareEventTypes(checker, aEvent)
        ) {
          var isExpected = false;
          for (var jdx = 0; jdx < eventSeq.length; jdx++) {
            isExpected = eventQueue.compareEvents(eventSeq[jdx], aEvent);
            if (isExpected) {
              break;
            }
          }

          if (!isExpected) {
            ok(
              false,
              "Unique type " +
                eventQueue.getEventTypeAsString(checker) +
                " event was handled."
            );
          }
        }
      }
    }

    var hasMatchedCheckers = false;
    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
      var eventSeq = this.mScenarios[scnIdx];

      // Check if handled event matches expected sync event.
      var nextChecker = this.getNextExpectedEvent(eventSeq);
      if (nextChecker) {
        if (eventQueue.compareEvents(nextChecker, aEvent)) {
          this.processMatchedChecker(aEvent, nextChecker, scnIdx, eventSeq.idx);
          hasMatchedCheckers = true;
          continue;
        }
      }

      // Check if handled event matches any expected async events.
      var haveUnmatchedAsync = false;
      for (idx = 0; idx < eventSeq.length; idx++) {
        if (eventSeq[idx] instanceof orderChecker && haveUnmatchedAsync) {
          break;
        }

        if (!eventSeq[idx].wasCaught) {
          haveUnmatchedAsync = true;
        }

        if (!eventSeq[idx].unexpected && eventSeq[idx].async) {
          if (eventQueue.compareEvents(eventSeq[idx], aEvent)) {
            this.processMatchedChecker(aEvent, eventSeq[idx], scnIdx, idx);
            hasMatchedCheckers = true;
            break;
          }
        }
      }
    }

    if (hasMatchedCheckers) {
      var invoker = this.getInvoker();
      if ("check" in invoker) {
        invoker.check(aEvent);
      }
    }

    for (idx = 0; idx < eventSeq.length; idx++) {
      if (!eventSeq[idx].wasCaught) {
        if (eventSeq[idx] instanceof orderChecker) {
          eventSeq[idx].wasCaught++;
        } else {
          break;
        }
      }
    }

    // If we don't have more events to wait then schedule next invoker.
    if (this.hasMatchedScenario()) {
      if (this.mNextInvokerStatus == kInvokerNotScheduled) {
        this.processNextInvokerInTimeout();
      } else if (this.mNextInvokerStatus == kInvokerCanceled) {
        this.setInvokerStatus(
          kInvokerPending,
          "Full match. Void the cancelation of next invoker processing"
        );
      }
      return;
    }

    // If we have scheduled a next invoker then cancel in case of match.
    if (this.mNextInvokerStatus == kInvokerPending && hasMatchedCheckers) {
      this.setInvokerStatus(
        kInvokerCanceled,
        "Cancel the scheduled invoker in case of match"
      );
    }
  };

  // Helpers
  this.processMatchedChecker = function eventQueue_function(
    aEvent,
    aMatchedChecker,
    aScenarioIdx,
    aEventIdx
  ) {
    aMatchedChecker.wasCaught++;

    if ("check" in aMatchedChecker) {
      aMatchedChecker.check(aEvent);
    }

    eventQueue.logEvent(
      aEvent,
      aMatchedChecker,
      aScenarioIdx,
      aEventIdx,
      this.areExpectedEventsLeft(),
      this.mNextInvokerStatus
    );
  };

  this.getNextExpectedEvent = function eventQueue_getNextExpectedEvent(
    aEventSeq
  ) {
    if (!("idx" in aEventSeq)) {
      aEventSeq.idx = 0;
    }

    while (
      aEventSeq.idx < aEventSeq.length &&
      (aEventSeq[aEventSeq.idx].unexpected ||
        aEventSeq[aEventSeq.idx].todo ||
        aEventSeq[aEventSeq.idx].async ||
        aEventSeq[aEventSeq.idx] instanceof orderChecker ||
        aEventSeq[aEventSeq.idx].wasCaught > 0)
    ) {
      aEventSeq.idx++;
    }

    return aEventSeq.idx != aEventSeq.length ? aEventSeq[aEventSeq.idx] : null;
  };

  this.areExpectedEventsLeft = function eventQueue_areExpectedEventsLeft(
    aScenario
  ) {
    function scenarioHasUnhandledExpectedEvent(aEventSeq) {
      // Check if we have unhandled async (can be anywhere in the sequance) or
      // sync expcected events yet.
      for (var idx = 0; idx < aEventSeq.length; idx++) {
        if (
          !aEventSeq[idx].unexpected &&
          !aEventSeq[idx].todo &&
          !aEventSeq[idx].wasCaught &&
          !(aEventSeq[idx] instanceof orderChecker)
        ) {
          return true;
        }
      }

      return false;
    }

    if (aScenario) {
      return scenarioHasUnhandledExpectedEvent(aScenario);
    }

    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
      var eventSeq = this.mScenarios[scnIdx];
      if (scenarioHasUnhandledExpectedEvent(eventSeq)) {
        return true;
      }
    }
    return false;
  };

  this.areAllEventsExpected = function eventQueue_areAllEventsExpected() {
    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
      var eventSeq = this.mScenarios[scnIdx];
      for (var idx = 0; idx < eventSeq.length; idx++) {
        if (eventSeq[idx].unexpected || eventSeq[idx].todo) {
          return false;
        }
      }
    }

    return true;
  };

  this.isUnexpectedEventScenario =
    function eventQueue_isUnexpectedEventsScenario(aScenario) {
      for (var idx = 0; idx < aScenario.length; idx++) {
        if (!aScenario[idx].unexpected && !aScenario[idx].todo) {
          break;
        }
      }

      return idx == aScenario.length;
    };

  this.hasUnexpectedEventsScenario =
    function eventQueue_hasUnexpectedEventsScenario() {
      if (this.getInvoker().noEventsOnAction) {
        return true;
      }

      for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
        if (this.isUnexpectedEventScenario(this.mScenarios[scnIdx])) {
          return true;
        }
      }

      return false;
    };

  this.hasMatchedScenario = function eventQueue_hasMatchedScenario() {
    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
      var scn = this.mScenarios[scnIdx];
      if (
        !this.isUnexpectedEventScenario(scn) &&
        !this.areExpectedEventsLeft(scn)
      ) {
        return true;
      }
    }
    return false;
  };

  this.getInvoker = function eventQueue_getInvoker() {
    return this.mInvokers[this.mIndex];
  };

  this.getNextInvoker = function eventQueue_getNextInvoker() {
    return this.mInvokers[++this.mIndex];
  };

  this.setEventHandler = function eventQueue_setEventHandler(aInvoker) {
    if (!("scenarios" in aInvoker) || !aInvoker.scenarios.length) {
      var eventSeq = aInvoker.eventSeq;
      var unexpectedEventSeq = aInvoker.unexpectedEventSeq;
      if (!eventSeq && !unexpectedEventSeq && this.mDefEventType) {
        eventSeq = [new invokerChecker(this.mDefEventType, aInvoker.DOMNode)];
      }

      if (eventSeq || unexpectedEventSeq) {
        defineScenario(aInvoker, eventSeq, unexpectedEventSeq);
      }
    }

    if (aInvoker.noEventsOnAction) {
      return true;
    }

    this.mScenarios = aInvoker.scenarios;
    if (!this.mScenarios || !this.mScenarios.length) {
      ok(false"Broken invoker '" + aInvoker.getID() + "'");
      return false;
    }

    // Register event listeners.
    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
      var eventSeq = this.mScenarios[scnIdx];

      if (gLogger.isEnabled()) {
        var msg =
          "scenario #" +
          scnIdx +
          ", registered events number: " +
          eventSeq.length;
        gLogger.logToConsole(msg);
        gLogger.logToDOM(msg, true);
      }

      // Do not warn about empty event sequances when more than one scenario
      // was registered.
      if (this.mScenarios.length == 1 && !eventSeq.length) {
        ok(
          false,
          "Broken scenario #" +
            scnIdx +
            " of invoker '" +
            aInvoker.getID() +
            "'. No registered events"
        );
        return false;
      }

      for (var idx = 0; idx < eventSeq.length; idx++) {
        eventSeq[idx].wasCaught = 0;
      }

      for (var idx = 0; idx < eventSeq.length; idx++) {
        if (gLogger.isEnabled()) {
          var msg = "registered";
          if (eventSeq[idx].unexpected) {
            msg += " unexpected";
          }
          if (eventSeq[idx].async) {
            msg += " async";
          }

          msg +=
            ": event type: " +
            eventQueue.getEventTypeAsString(eventSeq[idx]) +
            ", target: " +
            eventQueue.getEventTargetDescr(eventSeq[idx], true);

          gLogger.logToConsole(msg);
          gLogger.logToDOM(msg, true);
        }

        var eventType = eventSeq[idx].type;
        if (typeof eventType == "string") {
          // DOM event
          var target = eventQueue.getEventTarget(eventSeq[idx]);
          if (!target) {
            ok(false"no target for DOM event!");
            return false;
          }
          var phase = eventQueue.getEventPhase(eventSeq[idx]);
          target.addEventListener(eventType, this, phase);
        } else {
          // A11y event
          addA11yEventListener(eventType, this);
        }
      }
    }

    return true;
  };

  this.clearEventHandler = function eventQueue_clearEventHandler() {
    if (!this.mScenarios) {
      return;
    }

    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
      var eventSeq = this.mScenarios[scnIdx];
      for (var idx = 0; idx < eventSeq.length; idx++) {
        var eventType = eventSeq[idx].type;
        if (typeof eventType == "string") {
          // DOM event
          var target = eventQueue.getEventTarget(eventSeq[idx]);
          var phase = eventQueue.getEventPhase(eventSeq[idx]);
          target.removeEventListener(eventType, this, phase);
        } else {
          // A11y event
          removeA11yEventListener(eventType, this);
        }
      }
    }
    this.mScenarios = null;
  };

  this.getEventID = function eventQueue_getEventID(aChecker) {
    if ("getID" in aChecker) {
      return aChecker.getID();
    }

    var invoker = this.getInvoker();
    return invoker.getID();
  };

  this.setInvokerStatus = function eventQueue_setInvokerStatus(aStatus) {
    this.mNextInvokerStatus = aStatus;

    // Uncomment it to debug invoker processing logic.
    // gLogger.log(eventQueue.invokerStatusToMsg(aStatus, aLogMsg));
  };

  this.mDefEventType = aEventType;

  this.mInvokers = [];
  this.mIndex = -1;
  this.mScenarios = null;

  this.mNextInvokerStatus = kInvokerNotScheduled;
}

// //////////////////////////////////////////////////////////////////////////////
// eventQueue static members and constants

const kInvokerNotScheduled = 0;
const kInvokerPending = 1;
const kInvokerCanceled = 2;

eventQueue.getEventTypeAsString = function eventQueue_getEventTypeAsString(
  aEventOrChecker
) {
  if (Event.isInstance(aEventOrChecker)) {
    return aEventOrChecker.type;
  }

  if (aEventOrChecker instanceof nsIAccessibleEvent) {
    return eventTypeToString(aEventOrChecker.eventType);
  }

  return typeof aEventOrChecker.type == "string"
    ? aEventOrChecker.type
    : eventTypeToString(aEventOrChecker.type);
};

eventQueue.getEventTargetDescr = function eventQueue_getEventTargetDescr(
  aEventOrChecker,
  aDontForceTarget
) {
  if (Event.isInstance(aEventOrChecker)) {
    return prettyName(aEventOrChecker.originalTarget);
  }

  // XXXbz this block doesn't seem to be reachable...
  if (Event.isInstance(aEventOrChecker)) {
    return prettyName(aEventOrChecker.accessible);
  }

  var descr = aEventOrChecker.targetDescr;
  if (descr) {
    return descr;
  }

  if (aDontForceTarget) {
    return "no target description";
  }

  var target = "target" in aEventOrChecker ? aEventOrChecker.target : null;
  return prettyName(target);
};

eventQueue.getEventPhase = function eventQueue_getEventPhase(aChecker) {
  return "phase" in aChecker ? aChecker.phase : true;
};

eventQueue.getEventTarget = function eventQueue_getEventTarget(aChecker) {
  if ("eventTarget" in aChecker) {
    switch (aChecker.eventTarget) {
      case "element":
        return aChecker.target;
      case "document":
      default:
        return aChecker.target.ownerDocument;
    }
  }
  return aChecker.target.ownerDocument;
};

eventQueue.compareEventTypes = function eventQueue_compareEventTypes(
  aChecker,
  aEvent
) {
  var eventType = Event.isInstance(aEvent) ? aEvent.type : aEvent.eventType;
  return aChecker.type == eventType;
};

eventQueue.compareEvents = function eventQueue_compareEvents(aChecker, aEvent) {
  if (!eventQueue.compareEventTypes(aChecker, aEvent)) {
    return false;
  }

  // If checker provides "match" function then allow the checker to decide
  // whether event is matched.
  if ("match" in aChecker) {
    return aChecker.match(aEvent);
  }

  var target1 = aChecker.target;
  if (target1 instanceof nsIAccessible) {
    var target2 = Event.isInstance(aEvent)
      ? getAccessible(aEvent.target)
      : aEvent.accessible;

    return target1 == target2;
  }

  // If original target isn't suitable then extend interface to support target
  // (original target is used in test_elm_media.html).
  var target2 = Event.isInstance(aEvent)
    ? aEvent.originalTarget
    : aEvent.DOMNode;
  return target1 == target2;
};

eventQueue.isSameEvent = function eventQueue_isSameEvent(aChecker, aEvent) {
  // We don't have stored info about handled event other than its type and
  // target, thus we should filter text change and state change events since
  // they may occur on the same element because of complex changes.
  return (
    this.compareEvents(aChecker, aEvent) &&
    !(aEvent instanceof nsIAccessibleTextChangeEvent) &&
    !(aEvent instanceof nsIAccessibleStateChangeEvent)
  );
};

eventQueue.invokerStatusToMsg = function eventQueue_invokerStatusToMsg(
  aInvokerStatus,
  aMsg
) {
  var msg = "invoker status: ";
  switch (aInvokerStatus) {
    case kInvokerNotScheduled:
      msg += "not scheduled";
      break;
    case kInvokerPending:
      msg += "pending";
      break;
    case kInvokerCanceled:
      msg += "canceled";
      break;
  }

  if (aMsg) {
    msg += " (" + aMsg + ")";
  }

  return msg;
};

eventQueue.logEvent = function eventQueue_logEvent(
  aOrigEvent,
  aMatchedChecker,
  aScenarioIdx,
  aEventIdx,
  aAreExpectedEventsLeft,
  aInvokerStatus
) {
  // Dump DOM event information. Skip a11y event since it is dumped by
  // gA11yEventObserver.
  if (Event.isInstance(aOrigEvent)) {
    var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent);
    info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent);
    gLogger.logToDOM(info);
  }

  var infoMsg =
    "unhandled expected events: " +
    aAreExpectedEventsLeft +
    ", " +
    eventQueue.invokerStatusToMsg(aInvokerStatus);

  var currType = eventQueue.getEventTypeAsString(aMatchedChecker);
  var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker);
  var consoleMsg =
    "*****\nScenario " +
    aScenarioIdx +
    ", event " +
    aEventIdx +
    " matched: " +
    currType +
    "\n" +
    infoMsg +
    "\n*****";
  gLogger.logToConsole(consoleMsg);

  var emphText = "matched ";
  var msg =
    "EQ event, type: " +
    currType +
    ", target: " +
    currTargetDescr +
    ", " +
    infoMsg;
  gLogger.logToDOM(msg, true, emphText);
};

// //////////////////////////////////////////////////////////////////////////////
// Action sequence

/**
 * Deal with action sequence. Used when you need to execute couple of actions
 * each after other one.
 */

function sequence() {
  /**
   * Append new sequence item.
   *
   * @param  aProcessor  [in] object implementing interface
   *                      {
   *                        // execute item action
   *                        process: function() {},
   *                        // callback, is called when item was processed
   *                        onProcessed: function() {}
   *                      };
   * @param  aEventType  [in] event type of expected event on item action
   * @param  aTarget     [in] event target of expected event on item action
   * @param  aItemID     [in] identifier of item
   */

  this.append = function sequence_append(
    aProcessor,
    aEventType,
    aTarget,
    aItemID
  ) {
    var item = new sequenceItem(aProcessor, aEventType, aTarget, aItemID);
    this.items.push(item);
  };

  /**
   * Process next sequence item.
   */

  this.processNext = function sequence_processNext() {
    this.idx++;
    if (this.idx >= this.items.length) {
      ok(false"End of sequence: nothing to process!");
      SimpleTest.finish();
      return;
    }

    this.items[this.idx].startProcess();
  };

  this.items = [];
  this.idx = -1;
}

// //////////////////////////////////////////////////////////////////////////////
// Event queue invokers

/**
 * Defines a scenario of expected/unexpected events. Each invoker can have
 * one or more scenarios of events. Only one scenario must be completed.
 */

function defineScenario(aInvoker, aEventSeq, aUnexpectedEventSeq) {
  if (!("scenarios" in aInvoker)) {
    aInvoker.scenarios = [];
  }

  // Create unified event sequence concatenating expected and unexpected
  // events.
  if (!aEventSeq) {
    aEventSeq = [];
  }

  for (var idx = 0; idx < aEventSeq.length; idx++) {
    aEventSeq[idx].unexpected |= false;
    aEventSeq[idx].async |= false;
  }

  if (aUnexpectedEventSeq) {
    for (var idx = 0; idx < aUnexpectedEventSeq.length; idx++) {
      aUnexpectedEventSeq[idx].unexpected = true;
      aUnexpectedEventSeq[idx].async = false;
    }

    aEventSeq = aEventSeq.concat(aUnexpectedEventSeq);
  }

  aInvoker.scenarios.push(aEventSeq);
}

/**
 * Invokers defined below take a checker object (or array of checker objects).
 * An invoker listens for default event type registered in event queue object
 * until its checker is provided.
 *
 * Note, checker object or array of checker objects is optional.
 */


/**
 * Click invoker.
 */

function synthClick(aNodeOrID, aCheckerOrEventSeq, aArgs) {
  this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);

  this.invoke = function synthClick_invoke() {
    var targetNode = this.DOMNode;
    if (targetNode.nodeType == targetNode.DOCUMENT_NODE) {
      targetNode = this.DOMNode.body
        ? this.DOMNode.body
        : this.DOMNode.documentElement;
    }

    // Scroll the node into view, otherwise synth click may fail.
    if (isHTMLElement(targetNode)) {
      targetNode.scrollIntoView(true);
    } else if (isXULElement(targetNode)) {
      var targetAcc = getAccessible(targetNode);
      targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
    }

    var x = 1,
      y = 1;
    if (aArgs && "where" in aArgs) {
      if (aArgs.where == "right") {
        if (isHTMLElement(targetNode)) {
          x = targetNode.offsetWidth - 1;
        } else if (isXULElement(targetNode)) {
          x = targetNode.getBoundingClientRect().width - 1;
        }
      } else if (aArgs.where == "center") {
        if (isHTMLElement(targetNode)) {
          x = targetNode.offsetWidth / 2;
          y = targetNode.offsetHeight / 2;
        } else if (isXULElement(targetNode)) {
          x = targetNode.getBoundingClientRect().width / 2;
          y = targetNode.getBoundingClientRect().height / 2;
        }
      }
    }
    synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {});
  };

  this.finalCheck = function synthClick_finalCheck() {
    // Scroll top window back.
    window.top.scrollTo(0, 0);
  };

  this.getID = function synthClick_getID() {
    return prettyName(aNodeOrID) + " click";
  };
}

/**
 * Scrolls the node into view.
 */

function scrollIntoView(aNodeOrID, aCheckerOrEventSeq) {
  this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);

  this.invoke = function scrollIntoView_invoke() {
    var targetNode = this.DOMNode;
    if (isHTMLElement(targetNode)) {
      targetNode.scrollIntoView(true);
    } else if (isXULElement(targetNode)) {
      var targetAcc = getAccessible(targetNode);
      targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
    }
  };

  this.getID = function scrollIntoView_getID() {
    return prettyName(aNodeOrID) + " scrollIntoView";
  };
}

/**
 * Mouse move invoker.
 */

function synthMouseMove(aID, aCheckerOrEventSeq) {
  this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);

  this.invoke = function synthMouseMove_invoke() {
    synthesizeMouse(this.DOMNode, 5, 5, { type: "mousemove" });
    synthesizeMouse(this.DOMNode, 6, 6, { type: "mousemove" });
  };

  this.getID = function synthMouseMove_getID() {
    return prettyName(aID) + " mouse move";
  };
}

/**
 * General key press invoker.
 */

function synthKey(aNodeOrID, aKey, aArgs, aCheckerOrEventSeq) {
  this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);

  this.invoke = function synthKey_invoke() {
    synthesizeKey(this.mKey, this.mArgs, this.mWindow);
  };

  this.getID = function synthKey_getID() {
    var key = this.mKey;
    switch (this.mKey) {
      case "VK_TAB":
        key = "tab";
        break;
      case "VK_DOWN":
        key = "down";
        break;
      case "VK_UP":
        key = "up";
        break;
      case "VK_LEFT":
        key = "left";
        break;
      case "VK_RIGHT":
        key = "right";
        break;
      case "VK_HOME":
        key = "home";
        break;
      case "VK_END":
        key = "end";
        break;
      case "VK_ESCAPE":
        key = "escape";
        break;
      case "VK_RETURN":
        key = "enter";
        break;
    }
    if (aArgs) {
      if (aArgs.shiftKey) {
        key += " shift";
      }
      if (aArgs.ctrlKey) {
        key += " ctrl";
      }
      if (aArgs.altKey) {
        key += " alt";
      }
    }
    return prettyName(aNodeOrID) + " '" + key + " ' key";
  };

  this.mKey = aKey;
  this.mArgs = aArgs ? aArgs : {};
  this.mWindow = aArgs ? aArgs.window : null;
}

/**
 * Tab key invoker.
 */

function synthTab(aNodeOrID, aCheckerOrEventSeq, aWindow) {
  this.__proto__ = new synthKey(
    aNodeOrID,
    "VK_TAB",
    { shiftKey: false, window: aWindow },
    aCheckerOrEventSeq
  );
}

/**
 * Shift tab key invoker.
 */

function synthShiftTab(aNodeOrID, aCheckerOrEventSeq) {
  this.__proto__ = new synthKey(
    aNodeOrID,
    "VK_TAB",
    { shiftKey: true },
    aCheckerOrEventSeq
  );
}

/**
 * Escape key invoker.
 */

function synthEscapeKey(aNodeOrID, aCheckerOrEventSeq) {
  this.__proto__ = new synthKey(
    aNodeOrID,
    "VK_ESCAPE",
    null,
    aCheckerOrEventSeq
  );
}

/**
 * Down arrow key invoker.
 */

function synthDownKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
  this.__proto__ = new synthKey(
    aNodeOrID,
    "VK_DOWN",
    aArgs,
    aCheckerOrEventSeq
  );
}

/**
 * Up arrow key invoker.
 */

function synthUpKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
  this.__proto__ = new synthKey(aNodeOrID, "VK_UP", aArgs, aCheckerOrEventSeq);
}

/**
 * Left arrow key invoker.
 */

function synthLeftKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
  this.__proto__ = new synthKey(
    aNodeOrID,
    "VK_LEFT",
    aArgs,
    aCheckerOrEventSeq
  );
}

/**
 * Right arrow key invoker.
 */

function synthRightKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
  this.__proto__ = new synthKey(
    aNodeOrID,
    "VK_RIGHT",
    aArgs,
    aCheckerOrEventSeq
  );
}

/**
 * Home key invoker.
 */

function synthHomeKey(aNodeOrID, aCheckerOrEventSeq) {
  this.__proto__ = new synthKey(aNodeOrID, "VK_HOME"null, aCheckerOrEventSeq);
}

/**
 * End key invoker.
 */

function synthEndKey(aNodeOrID, aCheckerOrEventSeq) {
  this.__proto__ = new synthKey(aNodeOrID, "VK_END"null, aCheckerOrEventSeq);
}

/**
 * Enter key invoker
 */

function synthEnterKey(aID, aCheckerOrEventSeq) {
  this.__proto__ = new synthKey(aID, "VK_RETURN"null, aCheckerOrEventSeq);
}

/**
 * Synth alt + down arrow to open combobox.
 */

function synthOpenComboboxKey(aID, aCheckerOrEventSeq) {
  this.__proto__ = new synthDownKey(aID, aCheckerOrEventSeq, { altKey: true });

  this.getID = function synthOpenComboboxKey_getID() {
    return "open combobox (alt + down arrow) " + prettyName(aID);
  };
}

/**
 * Focus invoker.
 */

function synthFocus(aNodeOrID, aCheckerOrEventSeq) {
  var checkerOfEventSeq = aCheckerOrEventSeq
    ? aCheckerOrEventSeq
    : new focusChecker(aNodeOrID);
  this.__proto__ = new synthAction(aNodeOrID, checkerOfEventSeq);

  this.invoke = function synthFocus_invoke() {
    if (this.DOMNode.editor) {
      this.DOMNode.selectionStart = this.DOMNode.selectionEnd =
        this.DOMNode.value.length;
    }
    this.DOMNode.focus();
  };

  this.getID = function synthFocus_getID() {
    return prettyName(aNodeOrID) + " focus";
  };
}

/**
 * Focus invoker. Focus the HTML body of content document of iframe.
 */

function synthFocusOnFrame(aNodeOrID, aCheckerOrEventSeq) {
  var frameDoc = getNode(aNodeOrID).contentDocument;
  var checkerOrEventSeq = aCheckerOrEventSeq
    ? aCheckerOrEventSeq
    : new focusChecker(frameDoc);
  this.__proto__ = new synthAction(frameDoc, checkerOrEventSeq);

  this.invoke = function synthFocus_invoke() {
    this.DOMNode.body.focus();
  };

  this.getID = function synthFocus_getID() {
    return prettyName(aNodeOrID) + " frame document focus";
  };
}

/**
 * Change the current item when the widget doesn't have a focus.
 */

function changeCurrentItem(aID, aItemID) {
  this.eventSeq = [new nofocusChecker()];

  this.invoke = function changeCurrentItem_invoke() {
    var controlNode = getNode(aID);
    var itemNode = getNode(aItemID);

    // HTML
    if (controlNode.localName == "input") {
      if (controlNode.checked) {
        this.reportError();
      }

      controlNode.checked = true;
      return;
    }

    if (controlNode.localName == "select") {
      if (controlNode.selectedIndex == itemNode.index) {
        this.reportError();
      }

      controlNode.selectedIndex = itemNode.index;
      return;
    }

    // XUL
    if (controlNode.localName == "tree") {
      if (controlNode.currentIndex == aItemID) {
        this.reportError();
      }

      controlNode.currentIndex = aItemID;
      return;
    }

    if (controlNode.localName == "menulist") {
      if (controlNode.selectedItem == itemNode) {
        this.reportError();
      }

      controlNode.selectedItem = itemNode;
      return;
    }

    if (controlNode.currentItem == itemNode) {
      ok(
        false,
        "Error in test: proposed current item is already current" +
          prettyName(aID)
      );
    }

    controlNode.currentItem = itemNode;
  };

  this.getID = function changeCurrentItem_getID() {
    return "current item change for " + prettyName(aID);
  };

  this.reportError = function changeCurrentItem_reportError() {
    ok(
      false,
      "Error in test: proposed current item '" +
        aItemID +
        "' is already current"
    );
  };
}

/**
 * Toggle top menu invoker.
 */

function toggleTopMenu(aID, aCheckerOrEventSeq) {
  this.__proto__ = new synthKey(aID, "VK_ALT"null, aCheckerOrEventSeq);

  this.getID = function toggleTopMenu_getID() {
    return "toggle top menu on " + prettyName(aID);
  };
}

/**
 * Context menu invoker.
 */

function synthContextMenu(aID, aCheckerOrEventSeq) {
  this.__proto__ = new synthClick(aID, aCheckerOrEventSeq, {
    button: 0,
    type: "contextmenu",
  });

  this.getID = function synthContextMenu_getID() {
    return "context menu on " + prettyName(aID);
  };
}

/**
 * Open combobox, autocomplete and etc popup, check expandable states.
 */

function openCombobox(aComboboxID) {
  this.eventSeq = [
    new stateChangeChecker(STATE_EXPANDED, falsetrue, aComboboxID),
  ];

  this.invoke = function openCombobox_invoke() {
    getNode(aComboboxID).focus();
    synthesizeKey("VK_DOWN", { altKey: true });
  };

  this.getID = function openCombobox_getID() {
    return "open combobox " + prettyName(aComboboxID);
  };
}

/**
 * Close combobox, autocomplete and etc popup, check expandable states.
 */

function closeCombobox(aComboboxID) {
  this.eventSeq = [
    new stateChangeChecker(STATE_EXPANDED, falsefalse, aComboboxID),
  ];

  this.invoke = function closeCombobox_invoke() {
    synthesizeKey("KEY_Escape");
  };

  this.getID = function closeCombobox_getID() {
    return "close combobox " + prettyName(aComboboxID);
  };
}

/**
 * Select all invoker.
 */

function synthSelectAll(aNodeOrID, aCheckerOrEventSeq) {
  this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);

  this.invoke = function synthSelectAll_invoke() {
    if (ChromeUtils.getClassName(this.DOMNode) === "HTMLInputElement") {
      this.DOMNode.select();
    } else {
      window.getSelection().selectAllChildren(this.DOMNode);
    }
  };

  this.getID = function synthSelectAll_getID() {
    return aNodeOrID + " selectall";
  };
}

/**
 * Move the caret to the end of line.
 */

function moveToLineEnd(aID, aCaretOffset) {
  if (MAC) {
    this.__proto__ = new synthKey(
      aID,
      "VK_RIGHT",
      { metaKey: true },
      new caretMoveChecker(aCaretOffset, true, aID)
    );
  } else {
    this.__proto__ = new synthEndKey(
      aID,
      new caretMoveChecker(aCaretOffset, true, aID)
    );
  }

  this.getID = function moveToLineEnd_getID() {
    return "move to line end in " + prettyName(aID);
  };
}

/**
 * Move the caret to the end of previous line if any.
 */

function moveToPrevLineEnd(aID, aCaretOffset) {
  this.__proto__ = new synthAction(
    aID,
    new caretMoveChecker(aCaretOffset, true, aID)
  );

  this.invoke = function moveToPrevLineEnd_invoke() {
    synthesizeKey("KEY_ArrowUp");

    if (MAC) {
      synthesizeKey("Key_ArrowRight", { metaKey: true });
    } else {
      synthesizeKey("KEY_End");
    }
  };

  this.getID = function moveToPrevLineEnd_getID() {
    return "move to previous line end in " + prettyName(aID);
  };
}

/**
 * Move the caret to begining of the line.
 */

function moveToLineStart(aID, aCaretOffset) {
  if (MAC) {
    this.__proto__ = new synthKey(
      aID,
      "VK_LEFT",
      { metaKey: true },
      new caretMoveChecker(aCaretOffset, true, aID)
    );
  } else {
    this.__proto__ = new synthHomeKey(
      aID,
      new caretMoveChecker(aCaretOffset, true, aID)
    );
  }

  this.getID = function moveToLineEnd_getID() {
    return "move to line start in " + prettyName(aID);
  };
}

/**
 * Move the caret to begining of the text.
 */

function moveToTextStart(aID) {
  if (MAC) {
    this.__proto__ = new synthKey(
      aID,
      "VK_UP",
      { metaKey: true },
      new caretMoveChecker(0, true, aID)
    );
  } else {
    this.__proto__ = new synthKey(
      aID,
      "VK_HOME",
      { ctrlKey: true },
      new caretMoveChecker(0, true, aID)
    );
  }

  this.getID = function moveToTextStart_getID() {
    return "move to text start in " + prettyName(aID);
  };
}

/**
 * Move the caret in text accessible.
 */

function moveCaretToDOMPoint(
  aID,
  aDOMPointNodeID,
  aDOMPointOffset,
  aExpectedOffset,
  aFocusTargetID,
  aCheckFunc
) {
  this.target = getAccessible(aID, [nsIAccessibleText]);
  this.DOMPointNode = getNode(aDOMPointNodeID);
  this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
  this.focusNode = this.focus ? this.focus.DOMNode : null;

  this.invoke = function moveCaretToDOMPoint_invoke() {
    if (this.focusNode) {
      this.focusNode.focus();
    }

    var selection = this.DOMPointNode.ownerGlobal.getSelection();
    var selRange = selection.getRangeAt(0);
    selRange.setStart(this.DOMPointNode, aDOMPointOffset);
    selRange.collapse(true);

    selection.removeRange(selRange);
    selection.addRange(selRange);
  };

  this.getID = function moveCaretToDOMPoint_getID() {
    return (
      "Set caret on " +
      prettyName(aID) +
      " at point: " +
      prettyName(aDOMPointNodeID) +
      " node with offset " +
      aDOMPointOffset
    );
  };

  this.finalCheck = function moveCaretToDOMPoint_finalCheck() {
    if (aCheckFunc) {
      aCheckFunc.call();
    }
  };

  this.eventSeq = [new caretMoveChecker(aExpectedOffset, truethis.target)];

  if (this.focus) {
    this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
  }
}

/**
 * Set caret offset in text accessible.
 */

function setCaretOffset(aID, aOffset, aFocusTargetID) {
  this.target = getAccessible(aID, [nsIAccessibleText]);
  this.offset = aOffset == -1 ? this.target.characterCount : aOffset;
  this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;

  this.invoke = function setCaretOffset_invoke() {
    this.target.caretOffset = this.offset;
  };

  this.getID = function setCaretOffset_getID() {
    return "Set caretOffset on " + prettyName(aID) + " at " + this.offset;
  };

  this.eventSeq = [new caretMoveChecker(this.offset, truethis.target)];

  if (this.focus) {
    this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
  }
}

// //////////////////////////////////////////////////////////////////////////////
// Event queue checkers

/**
 * Common invoker checker (see eventSeq of eventQueue).
 */

function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync) {
  this.type = aEventType;
  this.async = aIsAsync;

  this.__defineGetter__("target", invokerChecker_targetGetter);
  this.__defineSetter__("target", invokerChecker_targetSetter);

  // implementation details
  function invokerChecker_targetGetter() {
    if (typeof this.mTarget == "function") {
      return this.mTarget.call(nullthis.mTargetFuncArg);
    }
    if (typeof this.mTarget == "string") {
      return getNode(this.mTarget);
    }

    return this.mTarget;
  }

  function invokerChecker_targetSetter(aValue) {
    this.mTarget = aValue;
    return this.mTarget;
  }

  this.__defineGetter__("targetDescr", invokerChecker_targetDescrGetter);

  function invokerChecker_targetDescrGetter() {
    if (typeof this.mTarget == "function") {
      return this.mTarget.name + ", arg: " + this.mTargetFuncArg;
    }

    return prettyName(this.mTarget);
  }

  this.mTarget = aTargetOrFunc;
  this.mTargetFuncArg = aTargetFuncArg;
}

/**
 * event checker that forces preceeding async events to happen before this
 * checker.
 */

function orderChecker() {
  // XXX it doesn't actually work to inherit from invokerChecker, but maybe we
  // should fix that?
  //  this.__proto__ = new invokerChecker(null, null, null, false);
}

/**
 * Generic invoker checker for todo events.
 */

function todo_invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) {
  this.__proto__ = new invokerChecker(
    aEventType,
    aTargetOrFunc,
    aTargetFuncArg,
    true
  );
  this.todo = true;
}

/**
 * Generic invoker checker for unexpected events.
 */

function unexpectedInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) {
  this.__proto__ = new invokerChecker(
    aEventType,
    aTargetOrFunc,
    aTargetFuncArg,
    true
  );

  this.unexpected = true;
}

/**
 * Common invoker checker for async events.
 */

function asyncInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) {
  this.__proto__ = new invokerChecker(
    aEventType,
    aTargetOrFunc,
    aTargetFuncArg,
    true
  );
}

function focusChecker(aTargetOrFunc, aTargetFuncArg) {
  this.__proto__ = new invokerChecker(
    EVENT_FOCUS,
    aTargetOrFunc,
    aTargetFuncArg,
    false
  );

  this.unique = true// focus event must be unique for invoker action

  this.check = function focusChecker_check(aEvent) {
    testStates(aEvent.accessible, STATE_FOCUSED);
  };
}

function nofocusChecker(aID) {
  this.__proto__ = new focusChecker(aID);
  this.unexpected = true;
}

/**
 * Text inserted/removed events checker.
 * @param aFromUser  [in, optional] kNotFromUserInput or kFromUserInput
 */

function textChangeChecker(
  aID,
  aStart,
  aEnd,
  aTextOrFunc,
  aIsInserted,
  aFromUser,
  aAsync
) {
  this.target = getNode(aID);
  this.type = aIsInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
  this.startOffset = aStart;
  this.endOffset = aEnd;
  this.textOrFunc = aTextOrFunc;
  this.async = aAsync;

  this.match = function stextChangeChecker_match(aEvent) {
    if (
      !(aEvent instanceof nsIAccessibleTextChangeEvent) ||
      aEvent.accessible !== getAccessible(this.target)
    ) {
      return false;
    }

    let tcEvent = aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
    let modifiedText =
      typeof this.textOrFunc === "function"
        ? this.textOrFunc()
        : this.textOrFunc;
    return modifiedText === tcEvent.modifiedText;
  };

  this.check = function textChangeChecker_check(aEvent) {
    aEvent.QueryInterface(nsIAccessibleTextChangeEvent);

    var modifiedText =
      typeof this.textOrFunc == "function"
        ? this.textOrFunc()
        : this.textOrFunc;
    var modifiedTextLen =
      this.endOffset == -1 ? modifiedText.length : aEnd - aStart;

    is(
      aEvent.start,
      this.startOffset,
      "Wrong start offset for " + prettyName(aID)
    );
    is(aEvent.length, modifiedTextLen, "Wrong length for " + prettyName(aID));
    var changeInfo = aIsInserted ? "inserted" : "removed";
    is(
      aEvent.isInserted,
      aIsInserted,
      "Text was " + changeInfo + " for " + prettyName(aID)
    );
    is(
      aEvent.modifiedText,
      modifiedText,
      "Wrong " + changeInfo + " text for " + prettyName(aID)
    );
    if (typeof aFromUser != "undefined") {
      is(
        aEvent.isFromUserInput,
        aFromUser,
        "wrong value of isFromUserInput() for " + prettyName(aID)
      );
    }
  };
}

/**
 * Caret move events checker.
 */

function caretMoveChecker(
  aCaretOffset,
  aIsSelectionCollapsed,
  aTargetOrFunc,
  aTargetFuncArg,
  aIsAsync
) {
  this.__proto__ = new invokerChecker(
    EVENT_TEXT_CARET_MOVED,
    aTargetOrFunc,
    aTargetFuncArg,
    aIsAsync
  );

  this.check = function caretMoveChecker_check(aEvent) {
    let evt = aEvent.QueryInterface(nsIAccessibleCaretMoveEvent);
    is(
      evt.caretOffset,
      aCaretOffset,
      "Wrong caret offset for " + prettyName(aEvent.accessible)
    );
    is(
      evt.isSelectionCollapsed,
      aIsSelectionCollapsed,
      "wrong collapsed value for " + prettyName(aEvent.accessible)
    );
  };
}

function asyncCaretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg) {
  this.__proto__ = new caretMoveChecker(
    aCaretOffset,
    true// Caret is collapsed
    aTargetOrFunc,
    aTargetFuncArg,
    true
  );
}

/**
 * Text selection change checker.
 */

function textSelectionChecker(
  aID,
  aStartOffset,
  aEndOffset,
  aRangeStartContainer,
  aRangeStartOffset,
  aRangeEndContainer,
  aRangeEndOffset
) {
  this.__proto__ = new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID);

  this.check = function textSelectionChecker_check(aEvent) {
    if (aStartOffset == aEndOffset) {
      ok(true"Collapsed selection triggered text selection change event.");
    } else {
      testTextGetSelection(aID, aStartOffset, aEndOffset, 0);

      // Test selection test range
      let selectionRanges = aEvent.QueryInterface(
        nsIAccessibleTextSelectionChangeEvent
      ).selectionRanges;
      let range = selectionRanges.queryElementAt(0, nsIAccessibleTextRange);
      is(
        range.startContainer,
        getAccessible(aRangeStartContainer),
        "correct range start container"
      );
      is(range.startOffset, aRangeStartOffset, "correct range start offset");
      is(range.endOffset, aRangeEndOffset, "correct range end offset");
      is(
        range.endContainer,
        getAccessible(aRangeEndContainer),
        "correct range end container"
      );
    }
  };
}

/**
 * Object attribute changed checker
 */

function objAttrChangedChecker(aID, aAttr) {
  this.__proto__ = new invokerChecker(EVENT_OBJECT_ATTRIBUTE_CHANGED, aID);

  this.check = function objAttrChangedChecker_check(aEvent) {
    var event = null;
    try {
      var event = aEvent.QueryInterface(
        nsIAccessibleObjectAttributeChangedEvent
      );
    } catch (e) {
      ok(false"Object attribute changed event was expected");
    }

    if (!event) {
      return;
    }

    is(
      event.changedAttribute,
      aAttr,
      "Wrong attribute name of the object attribute changed event."
    );
  };

  this.match = function objAttrChangedChecker_match(aEvent) {
    if (aEvent instanceof nsIAccessibleObjectAttributeChangedEvent) {
      var scEvent = aEvent.QueryInterface(
        nsIAccessibleObjectAttributeChangedEvent
      );
      return (
        aEvent.accessible == getAccessible(this.target) &&
        scEvent.changedAttribute == aAttr
      );
    }
    return false;
  };
}

/**
 * State change checker.
 */

function stateChangeChecker(
  aState,
  aIsExtraState,
  aIsEnabled,
  aTargetOrFunc,
  aTargetFuncArg,
  aIsAsync,
  aSkipCurrentStateCheck
) {
  this.__proto__ = new invokerChecker(
    EVENT_STATE_CHANGE,
    aTargetOrFunc,
    aTargetFuncArg,
    aIsAsync
  );

  this.check = function stateChangeChecker_check(aEvent) {
    var event = null;
    try {
      var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
    } catch (e) {
      ok(false"State change event was expected");
    }

    if (!event) {
      return;
    }

    is(
      event.isExtraState,
      aIsExtraState,
      "Wrong extra state bit of the statechange event."
    );
    isState(
      event.state,
      aState,
      aIsExtraState,
      "Wrong state of the statechange event."
    );
    is(event.isEnabled, aIsEnabled, "Wrong state of statechange event state");

    if (aSkipCurrentStateCheck) {
      todo(false"State checking was skipped!");
      return;
    }

    var state = aIsEnabled ? (aIsExtraState ? 0 : aState) : 0;
    var extraState = aIsEnabled ? (aIsExtraState ? aState : 0) : 0;
    var unxpdState = aIsEnabled ? 0 : aIsExtraState ? 0 : aState;
    var unxpdExtraState = aIsEnabled ? 0 : aIsExtraState ? aState : 0;
    testStates(
      event.accessible,
      state,
      extraState,
      unxpdState,
      unxpdExtraState
    );
  };

  this.match = function stateChangeChecker_match(aEvent) {
    if (aEvent instanceof nsIAccessibleStateChangeEvent) {
      var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
      return (
        aEvent.accessible == getAccessible(this.target) &&
        scEvent.state == aState
      );
    }
    return false;
  };
}

function asyncStateChangeChecker(
  aState,
  aIsExtraState,
  aIsEnabled,
  aTargetOrFunc,
  aTargetFuncArg
) {
  this.__proto__ = new stateChangeChecker(
    aState,
    aIsExtraState,
    aIsEnabled,
    aTargetOrFunc,
    aTargetFuncArg,
    true
  );
}

/**
 * Expanded state change checker.
 */

function expandedStateChecker(aIsEnabled, aTargetOrFunc, aTargetFuncArg) {
  this.__proto__ = new invokerChecker(
    EVENT_STATE_CHANGE,
    aTargetOrFunc,
    aTargetFuncArg
  );

  this.check = function expandedStateChecker_check(aEvent) {
    var event = null;
    try {
      var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
    } catch (e) {
      ok(false"State change event was expected");
    }

    if (!event) {
      return;
    }

    is(event.state, STATE_EXPANDED, "Wrong state of the statechange event.");
    is(
      event.isExtraState,
      false,
      "Wrong extra state bit of the statechange event."
    );
    is(event.isEnabled, aIsEnabled, "Wrong state of statechange event state");

    testStates(event.accessible, aIsEnabled ? STATE_EXPANDED : STATE_COLLAPSED);
  };
}

// //////////////////////////////////////////////////////////////////////////////
// Event sequances (array of predefined checkers)

/**
 * Event seq for single selection change.
 */

function selChangeSeq(aUnselectedID, aSelectedID) {
  if (!aUnselectedID) {
    return [
      new stateChangeChecker(STATE_SELECTED, falsetrue, aSelectedID),
      new invokerChecker(EVENT_SELECTION, aSelectedID),
    ];
  }

  // Return two possible scenarios: depending on widget type when selection is
  // moved the the order of items that get selected and unselected may vary.
  return [
    [
      new stateChangeChecker(STATE_SELECTED, falsefalse, aUnselectedID),
      new stateChangeChecker(STATE_SELECTED, falsetrue, aSelectedID),
      new invokerChecker(EVENT_SELECTION, aSelectedID),
    ],
    [
      new stateChangeChecker(STATE_SELECTED, falsetrue, aSelectedID),
      new stateChangeChecker(STATE_SELECTED, falsefalse, aUnselectedID),
      new invokerChecker(EVENT_SELECTION, aSelectedID),
    ],
  ];
}

/**
 * Event seq for item removed form the selection.
 */

function selRemoveSeq(aUnselectedID) {
  return [
    new stateChangeChecker(STATE_SELECTED, falsefalse, aUnselectedID),
    new invokerChecker(EVENT_SELECTION_REMOVE, aUnselectedID),
  ];
}

/**
 * Event seq for item added to the selection.
 */

function selAddSeq(aSelectedID) {
  return [
    new stateChangeChecker(STATE_SELECTED, falsetrue, aSelectedID),
    new invokerChecker(EVENT_SELECTION_ADD, aSelectedID),
  ];
}

// //////////////////////////////////////////////////////////////////////////////
// Private implementation details.
// //////////////////////////////////////////////////////////////////////////////

// //////////////////////////////////////////////////////////////////////////////
// General

var gA11yEventListeners = {};
var gA11yEventApplicantsCount = 0;

var gA11yEventObserver = {
  // eslint-disable-next-line complexity
  observe: function observe(aSubject, aTopic) {
    if (aTopic != "accessible-event") {
      return;
    }

    var event;
    try {
      event = aSubject.QueryInterface(nsIAccessibleEvent);
    } catch (ex) {
      // After a test is aborted (i.e. timed out by the harness), this exception is soon triggered.
      // Remove the leftover observer, otherwise it "leaks" to all the following tests.
      Services.obs.removeObserver(this"accessible-event");
      // Forward the exception, with added explanation.
      throw new Error(
        "[accessible/events.js, gA11yEventObserver.observe] This is expected " +
          `if a previous test has been aborted... Initial exception was: [ ${ex} ]`
      );
    }
    var listenersArray = gA11yEventListeners[event.eventType];

    var eventFromDumpArea = false;
    if (gLogger.isEnabled()) {
      // debug stuff
      eventFromDumpArea = true;

      var target = event.DOMNode;
      var dumpElm = gA11yEventDumpID
        ? document.getElementById(gA11yEventDumpID)
        : null;

      if (dumpElm) {
        var parent = target;
        while (parent && parent != dumpElm) {
          parent = parent.parentNode;
        }
      }

      if (!dumpElm || parent != dumpElm) {
        var type = eventTypeToString(event.eventType);
        var info = "Event type: " + type;

        if (event instanceof nsIAccessibleStateChangeEvent) {
          var stateStr = statesToString(
            event.isExtraState ? 0 : event.state,
            event.isExtraState ? event.state : 0
          );
          info += ", state: " + stateStr + ", is enabled: " + event.isEnabled;
        } else if (event instanceof nsIAccessibleTextChangeEvent) {
          info +=
            ", start: " +
            event.start +
            ", length: " +
            event.length +
            ", " +
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=94 G=93

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge