accessible/tests/mochitest/events.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial