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