1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/tests/mochitest/jsat/jsatcommon.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,512 @@ 1.4 +// A common module to run tests on the AccessFu module 1.5 + 1.6 +'use strict'; 1.7 + 1.8 +/*global isDeeply, getMainChromeWindow, SimpleTest, SpecialPowers, Logger, 1.9 + AccessFu, Utils, addMessageListener, currentTabDocument, currentBrowser*/ 1.10 + 1.11 +/** 1.12 + * A global variable holding an array of test functions. 1.13 + */ 1.14 +var gTestFuncs = []; 1.15 +/** 1.16 + * A global Iterator for the array of test functions. 1.17 + */ 1.18 +var gIterator; 1.19 + 1.20 +Components.utils.import('resource://gre/modules/Services.jsm'); 1.21 +Components.utils.import("resource://gre/modules/accessibility/Utils.jsm"); 1.22 +Components.utils.import("resource://gre/modules/accessibility/EventManager.jsm"); 1.23 +Components.utils.import("resource://gre/modules/accessibility/Gestures.jsm"); 1.24 + 1.25 +const dwellThreshold = GestureSettings.dwellThreshold; 1.26 +const swipeMaxDuration = GestureSettings.swipeMaxDuration; 1.27 +const maxConsecutiveGestureDelay = GestureSettings.maxConsecutiveGestureDelay; 1.28 + 1.29 +// https://bugzilla.mozilla.org/show_bug.cgi?id=1001945 - sometimes 1.30 +// SimpleTest.executeSoon timeout is bigger than the timer settings in 1.31 +// GestureSettings that causes intermittents. 1.32 +GestureSettings.dwellThreshold = dwellThreshold * 10; 1.33 +GestureSettings.swipeMaxDuration = swipeMaxDuration * 10; 1.34 +GestureSettings.maxConsecutiveGestureDelay = maxConsecutiveGestureDelay * 10; 1.35 + 1.36 +var AccessFuTest = { 1.37 + 1.38 + addFunc: function AccessFuTest_addFunc(aFunc) { 1.39 + if (aFunc) { 1.40 + gTestFuncs.push(aFunc); 1.41 + } 1.42 + }, 1.43 + 1.44 + _registerListener: function AccessFuTest__registerListener(aWaitForMessage, aListenerFunc) { 1.45 + var listener = { 1.46 + observe: function observe(aMessage) { 1.47 + // Ignore unexpected messages. 1.48 + if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) { 1.49 + return; 1.50 + } 1.51 + if (aMessage.message.indexOf(aWaitForMessage) < 0) { 1.52 + return; 1.53 + } 1.54 + aListenerFunc.apply(listener); 1.55 + } 1.56 + }; 1.57 + Services.console.registerListener(listener); 1.58 + return listener; 1.59 + }, 1.60 + 1.61 + on_log: function AccessFuTest_on_log(aWaitForMessage, aListenerFunc) { 1.62 + return this._registerListener(aWaitForMessage, aListenerFunc); 1.63 + }, 1.64 + 1.65 + off_log: function AccessFuTest_off_log(aListener) { 1.66 + Services.console.unregisterListener(aListener); 1.67 + }, 1.68 + 1.69 + once_log: function AccessFuTest_once_log(aWaitForMessage, aListenerFunc) { 1.70 + return this._registerListener(aWaitForMessage, 1.71 + function listenAndUnregister() { 1.72 + Services.console.unregisterListener(this); 1.73 + aListenerFunc(); 1.74 + }); 1.75 + }, 1.76 + 1.77 + _addObserver: function AccessFuTest__addObserver(aWaitForData, aListener) { 1.78 + var listener = function listener(aSubject, aTopic, aData) { 1.79 + var data = JSON.parse(aData)[1]; 1.80 + // Ignore non-relevant outputs. 1.81 + if (!data) { 1.82 + return; 1.83 + } 1.84 + isDeeply(data.details.actions, aWaitForData, "Data is correct"); 1.85 + aListener.apply(listener); 1.86 + }; 1.87 + Services.obs.addObserver(listener, 'accessfu-output', false); 1.88 + return listener; 1.89 + }, 1.90 + 1.91 + on: function AccessFuTest_on(aWaitForData, aListener) { 1.92 + return this._addObserver(aWaitForData, aListener); 1.93 + }, 1.94 + 1.95 + off: function AccessFuTest_off(aListener) { 1.96 + Services.obs.removeObserver(aListener, 'accessfu-output'); 1.97 + }, 1.98 + 1.99 + once: function AccessFuTest_once(aWaitForData, aListener) { 1.100 + return this._addObserver(aWaitForData, function observerAndRemove() { 1.101 + Services.obs.removeObserver(this, 'accessfu-output'); 1.102 + aListener(); 1.103 + }); 1.104 + }, 1.105 + 1.106 + _waitForExplicitFinish: false, 1.107 + 1.108 + waitForExplicitFinish: function AccessFuTest_waitForExplicitFinish() { 1.109 + this._waitForExplicitFinish = true; 1.110 + }, 1.111 + 1.112 + finish: function AccessFuTest_finish() { 1.113 + // Disable the console service logging. 1.114 + Logger.test = false; 1.115 + Logger.logLevel = Logger.INFO; 1.116 + // Reset Gesture Settings. 1.117 + GestureSettings.dwellThreshold = dwellThreshold; 1.118 + GestureSettings.swipeMaxDuration = swipeMaxDuration; 1.119 + GestureSettings.maxConsecutiveGestureDelay = maxConsecutiveGestureDelay; 1.120 + // Finish through idle callback to let AccessFu._disable complete. 1.121 + SimpleTest.executeSoon(function () { 1.122 + AccessFu.detach(); 1.123 + SimpleTest.finish(); 1.124 + }); 1.125 + }, 1.126 + 1.127 + nextTest: function AccessFuTest_nextTest() { 1.128 + var testFunc; 1.129 + try { 1.130 + // Get the next test function from the iterator. If none left, 1.131 + // StopIteration exception is thrown. 1.132 + testFunc = gIterator.next()[1]; 1.133 + } catch (ex) { 1.134 + // StopIteration exception. 1.135 + this.finish(); 1.136 + return; 1.137 + } 1.138 + testFunc(); 1.139 + }, 1.140 + 1.141 + runTests: function AccessFuTest_runTests() { 1.142 + if (gTestFuncs.length === 0) { 1.143 + ok(false, "No tests specified!"); 1.144 + SimpleTest.finish(); 1.145 + return; 1.146 + } 1.147 + 1.148 + // Create an Iterator for gTestFuncs array. 1.149 + gIterator = Iterator(gTestFuncs); // jshint ignore:line 1.150 + 1.151 + // Start AccessFu and put it in stand-by. 1.152 + Components.utils.import("resource://gre/modules/accessibility/AccessFu.jsm"); 1.153 + 1.154 + AccessFu.attach(getMainChromeWindow(window)); 1.155 + 1.156 + AccessFu.readyCallback = function readyCallback() { 1.157 + // Enable logging to the console service. 1.158 + Logger.test = true; 1.159 + Logger.logLevel = Logger.DEBUG; 1.160 + }; 1.161 + 1.162 + SpecialPowers.pushPrefEnv({ 1.163 + 'set': [['accessibility.accessfu.notify_output', 1], 1.164 + ['dom.mozSettings.enabled', true]] 1.165 + }, function () { 1.166 + if (AccessFuTest._waitForExplicitFinish) { 1.167 + // Run all test functions asynchronously. 1.168 + AccessFuTest.nextTest(); 1.169 + } else { 1.170 + // Run all test functions synchronously. 1.171 + [testFunc() for (testFunc of gTestFuncs)]; // jshint ignore:line 1.172 + AccessFuTest.finish(); 1.173 + } 1.174 + }); 1.175 + } 1.176 +}; 1.177 + 1.178 +function AccessFuContentTest(aFuncResultPairs) { 1.179 + this.queue = aFuncResultPairs; 1.180 +} 1.181 + 1.182 +AccessFuContentTest.prototype = { 1.183 + currentPair: null, 1.184 + 1.185 + start: function(aFinishedCallback) { 1.186 + Logger.logLevel = Logger.DEBUG; 1.187 + this.finishedCallback = aFinishedCallback; 1.188 + var self = this; 1.189 + 1.190 + // Get top content message manager, and set it up. 1.191 + this.mms = [Utils.getMessageManager(currentBrowser())]; 1.192 + this.setupMessageManager(this.mms[0], function () { 1.193 + // Get child message managers and set them up 1.194 + var frames = currentTabDocument().querySelectorAll('iframe'); 1.195 + if (frames.length === 0) { 1.196 + self.pump(); 1.197 + return; 1.198 + } 1.199 + 1.200 + var toSetup = 0; 1.201 + for (var i = 0; i < frames.length; i++ ) { 1.202 + var mm = Utils.getMessageManager(frames[i]); 1.203 + if (mm) { 1.204 + toSetup++; 1.205 + self.mms.push(mm); 1.206 + self.setupMessageManager(mm, function () { 1.207 + if (--toSetup === 0) { 1.208 + // All message managers are loaded and ready to go. 1.209 + self.pump(); 1.210 + } 1.211 + }); 1.212 + } 1.213 + } 1.214 + }); 1.215 + }, 1.216 + 1.217 + finish: function() { 1.218 + Logger.logLevel = Logger.INFO; 1.219 + for (var mm of this.mms) { 1.220 + mm.sendAsyncMessage('AccessFu:Stop'); 1.221 + } 1.222 + if (this.finishedCallback) { 1.223 + this.finishedCallback(); 1.224 + } 1.225 + }, 1.226 + 1.227 + setupMessageManager: function (aMessageManager, aCallback) { 1.228 + function contentScript() { 1.229 + addMessageListener('AccessFuTest:Focus', function (aMessage) { 1.230 + var elem = content.document.querySelector(aMessage.json.selector); 1.231 + if (elem) { 1.232 + if (aMessage.json.blur) { 1.233 + elem.blur(); 1.234 + } else { 1.235 + elem.focus(); 1.236 + } 1.237 + } 1.238 + }); 1.239 + } 1.240 + 1.241 + aMessageManager.addMessageListener('AccessFu:Present', this); 1.242 + aMessageManager.addMessageListener('AccessFu:CursorCleared', this); 1.243 + aMessageManager.addMessageListener('AccessFu:Ready', function () { 1.244 + aMessageManager.addMessageListener('AccessFu:ContentStarted', aCallback); 1.245 + aMessageManager.sendAsyncMessage('AccessFu:Start', 1.246 + { buildApp: 'browser', 1.247 + androidSdkVersion: Utils.AndroidSdkVersion, 1.248 + logLevel: 'DEBUG' }); 1.249 + }); 1.250 + 1.251 + aMessageManager.loadFrameScript( 1.252 + 'chrome://global/content/accessibility/content-script.js', false); 1.253 + aMessageManager.loadFrameScript( 1.254 + 'data:,(' + contentScript.toString() + ')();', false); 1.255 + }, 1.256 + 1.257 + pump: function() { 1.258 + this.currentPair = this.queue.shift(); 1.259 + 1.260 + if (this.currentPair) { 1.261 + if (this.currentPair[0] instanceof Function) { 1.262 + this.currentPair[0](this.mms[0]); 1.263 + } else if (this.currentPair[0]) { 1.264 + this.mms[0].sendAsyncMessage(this.currentPair[0].name, 1.265 + this.currentPair[0].json); 1.266 + } 1.267 + 1.268 + if (!this.currentPair[1]) { 1.269 + this.pump(); 1.270 + } 1.271 + } else { 1.272 + this.finish(); 1.273 + } 1.274 + }, 1.275 + 1.276 + receiveMessage: function(aMessage) { 1.277 + if (!this.currentPair) { 1.278 + return; 1.279 + } 1.280 + 1.281 + var expected = this.currentPair[1] || {}; 1.282 + 1.283 + // |expected| can simply be a name of a message, no more further testing. 1.284 + if (aMessage.name === expected) { 1.285 + ok(true, 'Received ' + expected); 1.286 + this.pump(); 1.287 + return; 1.288 + } 1.289 + 1.290 + var speech = this.extractUtterance(aMessage.json); 1.291 + var android = this.extractAndroid(aMessage.json, expected.android); 1.292 + if ((speech && expected.speak) || (android && expected.android)) { 1.293 + if (expected.speak) { 1.294 + (SimpleTest[expected.speak_checkFunc] || is)(speech, expected.speak, 1.295 + '"' + speech + '" spoken'); 1.296 + } 1.297 + 1.298 + if (expected.android) { 1.299 + var checkFunc = SimpleTest[expected.android_checkFunc] || ok; 1.300 + checkFunc.apply(SimpleTest, 1.301 + this.lazyCompare(android, expected.android)); 1.302 + } 1.303 + 1.304 + this.pump(); 1.305 + } 1.306 + 1.307 + }, 1.308 + 1.309 + lazyCompare: function lazyCompare(aReceived, aExpected) { 1.310 + var matches = true; 1.311 + var delta = []; 1.312 + for (var attr in aExpected) { 1.313 + var expected = aExpected[attr]; 1.314 + var received = aReceived !== undefined ? aReceived[attr] : null; 1.315 + if (typeof expected === 'object') { 1.316 + var [childMatches, childDelta] = this.lazyCompare(received, expected); 1.317 + if (!childMatches) { 1.318 + delta.push(attr + ' [ ' + childDelta + ' ]'); 1.319 + matches = false; 1.320 + } 1.321 + } else { 1.322 + if (received !== expected) { 1.323 + delta.push( 1.324 + attr + ' [ expected ' + expected + ' got ' + received + ' ]'); 1.325 + matches = false; 1.326 + } 1.327 + } 1.328 + } 1.329 + return [matches, delta.join(' ')]; 1.330 + }, 1.331 + 1.332 + extractUtterance: function(aData) { 1.333 + if (!aData) { 1.334 + return null; 1.335 + } 1.336 + 1.337 + for (var output of aData) { 1.338 + if (output && output.type === 'Speech') { 1.339 + for (var action of output.details.actions) { 1.340 + if (action && action.method == 'speak') { 1.341 + return action.data; 1.342 + } 1.343 + } 1.344 + } 1.345 + } 1.346 + 1.347 + return null; 1.348 + }, 1.349 + 1.350 + extractAndroid: function(aData, aExpectedEvents) { 1.351 + for (var output of aData) { 1.352 + if (output && output.type === 'Android') { 1.353 + for (var i in output.details) { 1.354 + // Only extract if event types match expected event types. 1.355 + var exp = aExpectedEvents ? aExpectedEvents[i] : null; 1.356 + if (!exp || (output.details[i].eventType !== exp.eventType)) { 1.357 + return null; 1.358 + } 1.359 + } 1.360 + return output.details; 1.361 + } 1.362 + } 1.363 + 1.364 + return null; 1.365 + } 1.366 +}; 1.367 + 1.368 +// Common content messages 1.369 + 1.370 +var ContentMessages = { 1.371 + simpleMoveFirst: { 1.372 + name: 'AccessFu:MoveCursor', 1.373 + json: { 1.374 + action: 'moveFirst', 1.375 + rule: 'Simple', 1.376 + inputType: 'gesture', 1.377 + origin: 'top' 1.378 + } 1.379 + }, 1.380 + 1.381 + simpleMoveLast: { 1.382 + name: 'AccessFu:MoveCursor', 1.383 + json: { 1.384 + action: 'moveLast', 1.385 + rule: 'Simple', 1.386 + inputType: 'gesture', 1.387 + origin: 'top' 1.388 + } 1.389 + }, 1.390 + 1.391 + simpleMoveNext: { 1.392 + name: 'AccessFu:MoveCursor', 1.393 + json: { 1.394 + action: 'moveNext', 1.395 + rule: 'Simple', 1.396 + inputType: 'gesture', 1.397 + origin: 'top' 1.398 + } 1.399 + }, 1.400 + 1.401 + simpleMovePrevious: { 1.402 + name: 'AccessFu:MoveCursor', 1.403 + json: { 1.404 + action: 'movePrevious', 1.405 + rule: 'Simple', 1.406 + inputType: 'gesture', 1.407 + origin: 'top' 1.408 + } 1.409 + }, 1.410 + 1.411 + clearCursor: { 1.412 + name: 'AccessFu:ClearCursor', 1.413 + json: { 1.414 + origin: 'top' 1.415 + } 1.416 + }, 1.417 + 1.418 + adjustRangeUp: { 1.419 + name: 'AccessFu:AdjustRange', 1.420 + json: { 1.421 + origin: 'top', 1.422 + direction: 'backward' 1.423 + } 1.424 + }, 1.425 + 1.426 + adjustRangeDown: { 1.427 + name: 'AccessFu:AdjustRange', 1.428 + json: { 1.429 + origin: 'top', 1.430 + direction: 'forward' 1.431 + } 1.432 + }, 1.433 + 1.434 + focusSelector: function focusSelector(aSelector, aBlur) { 1.435 + return { 1.436 + name: 'AccessFuTest:Focus', 1.437 + json: { 1.438 + selector: aSelector, 1.439 + blur: aBlur 1.440 + } 1.441 + }; 1.442 + }, 1.443 + 1.444 + activateCurrent: function activateCurrent(aOffset) { 1.445 + return { 1.446 + name: 'AccessFu:Activate', 1.447 + json: { 1.448 + origin: 'top', 1.449 + offset: aOffset 1.450 + } 1.451 + }; 1.452 + }, 1.453 + 1.454 + moveNextBy: function moveNextBy(aGranularity) { 1.455 + return { 1.456 + name: 'AccessFu:MoveByGranularity', 1.457 + json: { 1.458 + direction: 'Next', 1.459 + granularity: this._granularityMap[aGranularity] 1.460 + } 1.461 + }; 1.462 + }, 1.463 + 1.464 + movePreviousBy: function movePreviousBy(aGranularity) { 1.465 + return { 1.466 + name: 'AccessFu:MoveByGranularity', 1.467 + json: { 1.468 + direction: 'Previous', 1.469 + granularity: this._granularityMap[aGranularity] 1.470 + } 1.471 + }; 1.472 + }, 1.473 + 1.474 + moveCaretNextBy: function moveCaretNextBy(aGranularity) { 1.475 + return { 1.476 + name: 'AccessFu:MoveCaret', 1.477 + json: { 1.478 + direction: 'Next', 1.479 + granularity: this._granularityMap[aGranularity] 1.480 + } 1.481 + }; 1.482 + }, 1.483 + 1.484 + moveCaretPreviousBy: function moveCaretPreviousBy(aGranularity) { 1.485 + return { 1.486 + name: 'AccessFu:MoveCaret', 1.487 + json: { 1.488 + direction: 'Previous', 1.489 + granularity: this._granularityMap[aGranularity] 1.490 + } 1.491 + }; 1.492 + }, 1.493 + 1.494 + _granularityMap: { 1.495 + 'character': 1, // MOVEMENT_GRANULARITY_CHARACTER 1.496 + 'word': 2, // MOVEMENT_GRANULARITY_WORD 1.497 + 'paragraph': 8 // MOVEMENT_GRANULARITY_PARAGRAPH 1.498 + } 1.499 +}; 1.500 + 1.501 +var AndroidEvent = { 1.502 + VIEW_CLICKED: 0x01, 1.503 + VIEW_LONG_CLICKED: 0x02, 1.504 + VIEW_SELECTED: 0x04, 1.505 + VIEW_FOCUSED: 0x08, 1.506 + VIEW_TEXT_CHANGED: 0x10, 1.507 + WINDOW_STATE_CHANGED: 0x20, 1.508 + VIEW_HOVER_ENTER: 0x80, 1.509 + VIEW_HOVER_EXIT: 0x100, 1.510 + VIEW_SCROLLED: 0x1000, 1.511 + VIEW_TEXT_SELECTION_CHANGED: 0x2000, 1.512 + ANNOUNCEMENT: 0x4000, 1.513 + VIEW_ACCESSIBILITY_FOCUSED: 0x8000, 1.514 + VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: 0x20000 1.515 +};