Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 // A common module to run tests on the AccessFu module
3 'use strict';
5 /*global isDeeply, getMainChromeWindow, SimpleTest, SpecialPowers, Logger,
6 AccessFu, Utils, addMessageListener, currentTabDocument, currentBrowser*/
8 /**
9 * A global variable holding an array of test functions.
10 */
11 var gTestFuncs = [];
12 /**
13 * A global Iterator for the array of test functions.
14 */
15 var gIterator;
17 Components.utils.import('resource://gre/modules/Services.jsm');
18 Components.utils.import("resource://gre/modules/accessibility/Utils.jsm");
19 Components.utils.import("resource://gre/modules/accessibility/EventManager.jsm");
20 Components.utils.import("resource://gre/modules/accessibility/Gestures.jsm");
22 const dwellThreshold = GestureSettings.dwellThreshold;
23 const swipeMaxDuration = GestureSettings.swipeMaxDuration;
24 const maxConsecutiveGestureDelay = GestureSettings.maxConsecutiveGestureDelay;
26 // https://bugzilla.mozilla.org/show_bug.cgi?id=1001945 - sometimes
27 // SimpleTest.executeSoon timeout is bigger than the timer settings in
28 // GestureSettings that causes intermittents.
29 GestureSettings.dwellThreshold = dwellThreshold * 10;
30 GestureSettings.swipeMaxDuration = swipeMaxDuration * 10;
31 GestureSettings.maxConsecutiveGestureDelay = maxConsecutiveGestureDelay * 10;
33 var AccessFuTest = {
35 addFunc: function AccessFuTest_addFunc(aFunc) {
36 if (aFunc) {
37 gTestFuncs.push(aFunc);
38 }
39 },
41 _registerListener: function AccessFuTest__registerListener(aWaitForMessage, aListenerFunc) {
42 var listener = {
43 observe: function observe(aMessage) {
44 // Ignore unexpected messages.
45 if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) {
46 return;
47 }
48 if (aMessage.message.indexOf(aWaitForMessage) < 0) {
49 return;
50 }
51 aListenerFunc.apply(listener);
52 }
53 };
54 Services.console.registerListener(listener);
55 return listener;
56 },
58 on_log: function AccessFuTest_on_log(aWaitForMessage, aListenerFunc) {
59 return this._registerListener(aWaitForMessage, aListenerFunc);
60 },
62 off_log: function AccessFuTest_off_log(aListener) {
63 Services.console.unregisterListener(aListener);
64 },
66 once_log: function AccessFuTest_once_log(aWaitForMessage, aListenerFunc) {
67 return this._registerListener(aWaitForMessage,
68 function listenAndUnregister() {
69 Services.console.unregisterListener(this);
70 aListenerFunc();
71 });
72 },
74 _addObserver: function AccessFuTest__addObserver(aWaitForData, aListener) {
75 var listener = function listener(aSubject, aTopic, aData) {
76 var data = JSON.parse(aData)[1];
77 // Ignore non-relevant outputs.
78 if (!data) {
79 return;
80 }
81 isDeeply(data.details.actions, aWaitForData, "Data is correct");
82 aListener.apply(listener);
83 };
84 Services.obs.addObserver(listener, 'accessfu-output', false);
85 return listener;
86 },
88 on: function AccessFuTest_on(aWaitForData, aListener) {
89 return this._addObserver(aWaitForData, aListener);
90 },
92 off: function AccessFuTest_off(aListener) {
93 Services.obs.removeObserver(aListener, 'accessfu-output');
94 },
96 once: function AccessFuTest_once(aWaitForData, aListener) {
97 return this._addObserver(aWaitForData, function observerAndRemove() {
98 Services.obs.removeObserver(this, 'accessfu-output');
99 aListener();
100 });
101 },
103 _waitForExplicitFinish: false,
105 waitForExplicitFinish: function AccessFuTest_waitForExplicitFinish() {
106 this._waitForExplicitFinish = true;
107 },
109 finish: function AccessFuTest_finish() {
110 // Disable the console service logging.
111 Logger.test = false;
112 Logger.logLevel = Logger.INFO;
113 // Reset Gesture Settings.
114 GestureSettings.dwellThreshold = dwellThreshold;
115 GestureSettings.swipeMaxDuration = swipeMaxDuration;
116 GestureSettings.maxConsecutiveGestureDelay = maxConsecutiveGestureDelay;
117 // Finish through idle callback to let AccessFu._disable complete.
118 SimpleTest.executeSoon(function () {
119 AccessFu.detach();
120 SimpleTest.finish();
121 });
122 },
124 nextTest: function AccessFuTest_nextTest() {
125 var testFunc;
126 try {
127 // Get the next test function from the iterator. If none left,
128 // StopIteration exception is thrown.
129 testFunc = gIterator.next()[1];
130 } catch (ex) {
131 // StopIteration exception.
132 this.finish();
133 return;
134 }
135 testFunc();
136 },
138 runTests: function AccessFuTest_runTests() {
139 if (gTestFuncs.length === 0) {
140 ok(false, "No tests specified!");
141 SimpleTest.finish();
142 return;
143 }
145 // Create an Iterator for gTestFuncs array.
146 gIterator = Iterator(gTestFuncs); // jshint ignore:line
148 // Start AccessFu and put it in stand-by.
149 Components.utils.import("resource://gre/modules/accessibility/AccessFu.jsm");
151 AccessFu.attach(getMainChromeWindow(window));
153 AccessFu.readyCallback = function readyCallback() {
154 // Enable logging to the console service.
155 Logger.test = true;
156 Logger.logLevel = Logger.DEBUG;
157 };
159 SpecialPowers.pushPrefEnv({
160 'set': [['accessibility.accessfu.notify_output', 1],
161 ['dom.mozSettings.enabled', true]]
162 }, function () {
163 if (AccessFuTest._waitForExplicitFinish) {
164 // Run all test functions asynchronously.
165 AccessFuTest.nextTest();
166 } else {
167 // Run all test functions synchronously.
168 [testFunc() for (testFunc of gTestFuncs)]; // jshint ignore:line
169 AccessFuTest.finish();
170 }
171 });
172 }
173 };
175 function AccessFuContentTest(aFuncResultPairs) {
176 this.queue = aFuncResultPairs;
177 }
179 AccessFuContentTest.prototype = {
180 currentPair: null,
182 start: function(aFinishedCallback) {
183 Logger.logLevel = Logger.DEBUG;
184 this.finishedCallback = aFinishedCallback;
185 var self = this;
187 // Get top content message manager, and set it up.
188 this.mms = [Utils.getMessageManager(currentBrowser())];
189 this.setupMessageManager(this.mms[0], function () {
190 // Get child message managers and set them up
191 var frames = currentTabDocument().querySelectorAll('iframe');
192 if (frames.length === 0) {
193 self.pump();
194 return;
195 }
197 var toSetup = 0;
198 for (var i = 0; i < frames.length; i++ ) {
199 var mm = Utils.getMessageManager(frames[i]);
200 if (mm) {
201 toSetup++;
202 self.mms.push(mm);
203 self.setupMessageManager(mm, function () {
204 if (--toSetup === 0) {
205 // All message managers are loaded and ready to go.
206 self.pump();
207 }
208 });
209 }
210 }
211 });
212 },
214 finish: function() {
215 Logger.logLevel = Logger.INFO;
216 for (var mm of this.mms) {
217 mm.sendAsyncMessage('AccessFu:Stop');
218 }
219 if (this.finishedCallback) {
220 this.finishedCallback();
221 }
222 },
224 setupMessageManager: function (aMessageManager, aCallback) {
225 function contentScript() {
226 addMessageListener('AccessFuTest:Focus', function (aMessage) {
227 var elem = content.document.querySelector(aMessage.json.selector);
228 if (elem) {
229 if (aMessage.json.blur) {
230 elem.blur();
231 } else {
232 elem.focus();
233 }
234 }
235 });
236 }
238 aMessageManager.addMessageListener('AccessFu:Present', this);
239 aMessageManager.addMessageListener('AccessFu:CursorCleared', this);
240 aMessageManager.addMessageListener('AccessFu:Ready', function () {
241 aMessageManager.addMessageListener('AccessFu:ContentStarted', aCallback);
242 aMessageManager.sendAsyncMessage('AccessFu:Start',
243 { buildApp: 'browser',
244 androidSdkVersion: Utils.AndroidSdkVersion,
245 logLevel: 'DEBUG' });
246 });
248 aMessageManager.loadFrameScript(
249 'chrome://global/content/accessibility/content-script.js', false);
250 aMessageManager.loadFrameScript(
251 'data:,(' + contentScript.toString() + ')();', false);
252 },
254 pump: function() {
255 this.currentPair = this.queue.shift();
257 if (this.currentPair) {
258 if (this.currentPair[0] instanceof Function) {
259 this.currentPair[0](this.mms[0]);
260 } else if (this.currentPair[0]) {
261 this.mms[0].sendAsyncMessage(this.currentPair[0].name,
262 this.currentPair[0].json);
263 }
265 if (!this.currentPair[1]) {
266 this.pump();
267 }
268 } else {
269 this.finish();
270 }
271 },
273 receiveMessage: function(aMessage) {
274 if (!this.currentPair) {
275 return;
276 }
278 var expected = this.currentPair[1] || {};
280 // |expected| can simply be a name of a message, no more further testing.
281 if (aMessage.name === expected) {
282 ok(true, 'Received ' + expected);
283 this.pump();
284 return;
285 }
287 var speech = this.extractUtterance(aMessage.json);
288 var android = this.extractAndroid(aMessage.json, expected.android);
289 if ((speech && expected.speak) || (android && expected.android)) {
290 if (expected.speak) {
291 (SimpleTest[expected.speak_checkFunc] || is)(speech, expected.speak,
292 '"' + speech + '" spoken');
293 }
295 if (expected.android) {
296 var checkFunc = SimpleTest[expected.android_checkFunc] || ok;
297 checkFunc.apply(SimpleTest,
298 this.lazyCompare(android, expected.android));
299 }
301 this.pump();
302 }
304 },
306 lazyCompare: function lazyCompare(aReceived, aExpected) {
307 var matches = true;
308 var delta = [];
309 for (var attr in aExpected) {
310 var expected = aExpected[attr];
311 var received = aReceived !== undefined ? aReceived[attr] : null;
312 if (typeof expected === 'object') {
313 var [childMatches, childDelta] = this.lazyCompare(received, expected);
314 if (!childMatches) {
315 delta.push(attr + ' [ ' + childDelta + ' ]');
316 matches = false;
317 }
318 } else {
319 if (received !== expected) {
320 delta.push(
321 attr + ' [ expected ' + expected + ' got ' + received + ' ]');
322 matches = false;
323 }
324 }
325 }
326 return [matches, delta.join(' ')];
327 },
329 extractUtterance: function(aData) {
330 if (!aData) {
331 return null;
332 }
334 for (var output of aData) {
335 if (output && output.type === 'Speech') {
336 for (var action of output.details.actions) {
337 if (action && action.method == 'speak') {
338 return action.data;
339 }
340 }
341 }
342 }
344 return null;
345 },
347 extractAndroid: function(aData, aExpectedEvents) {
348 for (var output of aData) {
349 if (output && output.type === 'Android') {
350 for (var i in output.details) {
351 // Only extract if event types match expected event types.
352 var exp = aExpectedEvents ? aExpectedEvents[i] : null;
353 if (!exp || (output.details[i].eventType !== exp.eventType)) {
354 return null;
355 }
356 }
357 return output.details;
358 }
359 }
361 return null;
362 }
363 };
365 // Common content messages
367 var ContentMessages = {
368 simpleMoveFirst: {
369 name: 'AccessFu:MoveCursor',
370 json: {
371 action: 'moveFirst',
372 rule: 'Simple',
373 inputType: 'gesture',
374 origin: 'top'
375 }
376 },
378 simpleMoveLast: {
379 name: 'AccessFu:MoveCursor',
380 json: {
381 action: 'moveLast',
382 rule: 'Simple',
383 inputType: 'gesture',
384 origin: 'top'
385 }
386 },
388 simpleMoveNext: {
389 name: 'AccessFu:MoveCursor',
390 json: {
391 action: 'moveNext',
392 rule: 'Simple',
393 inputType: 'gesture',
394 origin: 'top'
395 }
396 },
398 simpleMovePrevious: {
399 name: 'AccessFu:MoveCursor',
400 json: {
401 action: 'movePrevious',
402 rule: 'Simple',
403 inputType: 'gesture',
404 origin: 'top'
405 }
406 },
408 clearCursor: {
409 name: 'AccessFu:ClearCursor',
410 json: {
411 origin: 'top'
412 }
413 },
415 adjustRangeUp: {
416 name: 'AccessFu:AdjustRange',
417 json: {
418 origin: 'top',
419 direction: 'backward'
420 }
421 },
423 adjustRangeDown: {
424 name: 'AccessFu:AdjustRange',
425 json: {
426 origin: 'top',
427 direction: 'forward'
428 }
429 },
431 focusSelector: function focusSelector(aSelector, aBlur) {
432 return {
433 name: 'AccessFuTest:Focus',
434 json: {
435 selector: aSelector,
436 blur: aBlur
437 }
438 };
439 },
441 activateCurrent: function activateCurrent(aOffset) {
442 return {
443 name: 'AccessFu:Activate',
444 json: {
445 origin: 'top',
446 offset: aOffset
447 }
448 };
449 },
451 moveNextBy: function moveNextBy(aGranularity) {
452 return {
453 name: 'AccessFu:MoveByGranularity',
454 json: {
455 direction: 'Next',
456 granularity: this._granularityMap[aGranularity]
457 }
458 };
459 },
461 movePreviousBy: function movePreviousBy(aGranularity) {
462 return {
463 name: 'AccessFu:MoveByGranularity',
464 json: {
465 direction: 'Previous',
466 granularity: this._granularityMap[aGranularity]
467 }
468 };
469 },
471 moveCaretNextBy: function moveCaretNextBy(aGranularity) {
472 return {
473 name: 'AccessFu:MoveCaret',
474 json: {
475 direction: 'Next',
476 granularity: this._granularityMap[aGranularity]
477 }
478 };
479 },
481 moveCaretPreviousBy: function moveCaretPreviousBy(aGranularity) {
482 return {
483 name: 'AccessFu:MoveCaret',
484 json: {
485 direction: 'Previous',
486 granularity: this._granularityMap[aGranularity]
487 }
488 };
489 },
491 _granularityMap: {
492 'character': 1, // MOVEMENT_GRANULARITY_CHARACTER
493 'word': 2, // MOVEMENT_GRANULARITY_WORD
494 'paragraph': 8 // MOVEMENT_GRANULARITY_PARAGRAPH
495 }
496 };
498 var AndroidEvent = {
499 VIEW_CLICKED: 0x01,
500 VIEW_LONG_CLICKED: 0x02,
501 VIEW_SELECTED: 0x04,
502 VIEW_FOCUSED: 0x08,
503 VIEW_TEXT_CHANGED: 0x10,
504 WINDOW_STATE_CHANGED: 0x20,
505 VIEW_HOVER_ENTER: 0x80,
506 VIEW_HOVER_EXIT: 0x100,
507 VIEW_SCROLLED: 0x1000,
508 VIEW_TEXT_SELECTION_CHANGED: 0x2000,
509 ANNOUNCEMENT: 0x4000,
510 VIEW_ACCESSIBILITY_FOCUSED: 0x8000,
511 VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: 0x20000
512 };