summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/general/browser_gestureSupport.js
blob: b31cad31d0a186d4b98928840ad0b8c30d4022e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Simple gestures tests
//
// These tests require the ability to disable the fact that the
// Firefox chrome intentionally prevents "simple gesture" events from
// reaching web content.

var test_utils;
var test_commandset;
var test_prefBranch = "browser.gesture.";

function test()
{
  waitForExplicitFinish();

  // Disable the default gestures support during the test
  gGestureSupport.init(false);

  test_utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
    getInterface(Components.interfaces.nsIDOMWindowUtils);

  // Run the tests of "simple gesture" events generally
  test_EnsureConstantsAreDisjoint();
  test_TestEventListeners();
  test_TestEventCreation();

  // Reenable the default gestures support. The remaining tests target
  // the Firefox gesture functionality.
  gGestureSupport.init(true);

  // Test Firefox's gestures support.
  test_commandset = document.getElementById("mainCommandSet");
  test_swipeGestures();
  test_latchedGesture("pinch", "out", "in", "MozMagnifyGesture");
  test_thresholdGesture("pinch", "out", "in", "MozMagnifyGesture");
  test_rotateGestures();
}

var test_eventCount = 0;
var test_expectedType;
var test_expectedDirection;
var test_expectedDelta;
var test_expectedModifiers;
var test_expectedClickCount;
var test_imageTab;

function test_gestureListener(evt)
{
  is(evt.type, test_expectedType,
     "evt.type (" + evt.type + ") does not match expected value");
  is(evt.target, test_utils.elementFromPoint(20, 20, false, false),
     "evt.target (" + evt.target + ") does not match expected value");
  is(evt.clientX, 20,
     "evt.clientX (" + evt.clientX + ") does not match expected value");
  is(evt.clientY, 20,
     "evt.clientY (" + evt.clientY + ") does not match expected value");
  isnot(evt.screenX, 0,
        "evt.screenX (" + evt.screenX + ") does not match expected value");
  isnot(evt.screenY, 0,
        "evt.screenY (" + evt.screenY + ") does not match expected value");

  is(evt.direction, test_expectedDirection,
     "evt.direction (" + evt.direction + ") does not match expected value");
  is(evt.delta, test_expectedDelta,
     "evt.delta (" + evt.delta + ") does not match expected value");

  is(evt.shiftKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.SHIFT_MASK) != 0,
     "evt.shiftKey did not match expected value");
  is(evt.ctrlKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.CONTROL_MASK) != 0,
     "evt.ctrlKey did not match expected value");
  is(evt.altKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.ALT_MASK) != 0,
     "evt.altKey did not match expected value");
  is(evt.metaKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.META_MASK) != 0,
     "evt.metaKey did not match expected value");

  if (evt.type == "MozTapGesture") {
    is(evt.clickCount, test_expectedClickCount, "evt.clickCount does not match");
  }

  test_eventCount++;
}

function test_helper1(type, direction, delta, modifiers)
{
  // Setup the expected values
  test_expectedType = type;
  test_expectedDirection = direction;
  test_expectedDelta = delta;
  test_expectedModifiers = modifiers;

  let expectedEventCount = test_eventCount + 1;

  document.addEventListener(type, test_gestureListener, true);
  test_utils.sendSimpleGestureEvent(type, 20, 20, direction, delta, modifiers);
  document.removeEventListener(type, test_gestureListener, true);

  is(expectedEventCount, test_eventCount, "Event (" + type + ") was never received by event listener");
}

function test_clicks(type, clicks)
{
  // Setup the expected values
  test_expectedType = type;
  test_expectedDirection = 0;
  test_expectedDelta = 0;
  test_expectedModifiers = 0;
  test_expectedClickCount = clicks;

  let expectedEventCount = test_eventCount + 1;

  document.addEventListener(type, test_gestureListener, true);
  test_utils.sendSimpleGestureEvent(type, 20, 20, 0, 0, 0, clicks);
  document.removeEventListener(type, test_gestureListener, true);

  is(expectedEventCount, test_eventCount, "Event (" + type + ") was never received by event listener");
}

function test_TestEventListeners()
{
  let e = test_helper1;  // easier to type this name

  // Swipe gesture animation events
  e("MozSwipeGestureStart", 0, -0.7, 0);
  e("MozSwipeGestureUpdate", 0, -0.4, 0);
  e("MozSwipeGestureEnd", 0, 0, 0);
  e("MozSwipeGestureStart", 0, 0.6, 0);
  e("MozSwipeGestureUpdate", 0, 0.3, 0);
  e("MozSwipeGestureEnd", 0, 1, 0);

  // Swipe gesture event
  e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
  e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
  e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_UP, 0.0, 0);
  e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_DOWN, 0.0, 0);
  e("MozSwipeGesture",
    SimpleGestureEvent.DIRECTION_UP | SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
  e("MozSwipeGesture",
    SimpleGestureEvent.DIRECTION_DOWN | SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
  e("MozSwipeGesture",
    SimpleGestureEvent.DIRECTION_UP | SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
  e("MozSwipeGesture",
    SimpleGestureEvent.DIRECTION_DOWN | SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);

  // magnify gesture events
  e("MozMagnifyGestureStart", 0, 50.0, 0);
  e("MozMagnifyGestureUpdate", 0, -25.0, 0);
  e("MozMagnifyGestureUpdate", 0, 5.0, 0);
  e("MozMagnifyGesture", 0, 30.0, 0);

  // rotate gesture events
  e("MozRotateGestureStart", SimpleGestureEvent.ROTATION_CLOCKWISE, 33.0, 0);
  e("MozRotateGestureUpdate", SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE, -13.0, 0);
  e("MozRotateGestureUpdate", SimpleGestureEvent.ROTATION_CLOCKWISE, 13.0, 0);
  e("MozRotateGesture", SimpleGestureEvent.ROTATION_CLOCKWISE, 33.0, 0);

  // Tap and presstap gesture events
  test_clicks("MozTapGesture", 1);
  test_clicks("MozTapGesture", 2);
  test_clicks("MozTapGesture", 3);
  test_clicks("MozPressTapGesture", 1);

  // simple delivery test for edgeui gestures
  e("MozEdgeUIStarted", 0, 0, 0);
  e("MozEdgeUICanceled", 0, 0, 0);
  e("MozEdgeUICompleted", 0, 0, 0);

  // event.shiftKey
  let modifier = Components.interfaces.nsIDOMEvent.SHIFT_MASK;
  e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);

  // event.metaKey
  modifier = Components.interfaces.nsIDOMEvent.META_MASK;
  e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);

  // event.altKey
  modifier = Components.interfaces.nsIDOMEvent.ALT_MASK;
  e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);

  // event.ctrlKey
  modifier = Components.interfaces.nsIDOMEvent.CONTROL_MASK;
  e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
}

function test_eventDispatchListener(evt)
{
  test_eventCount++;
  evt.stopPropagation();
}

function test_helper2(type, direction, delta, altKey, ctrlKey, shiftKey, metaKey)
{
  let event = null;
  let successful;

  try {
    event = document.createEvent("SimpleGestureEvent");
    successful = true;
  }
  catch (ex) {
    successful = false;
  }
  ok(successful, "Unable to create SimpleGestureEvent");

  try {
    event.initSimpleGestureEvent(type, true, true, window, 1,
                                 10, 10, 10, 10,
                                 ctrlKey, altKey, shiftKey, metaKey,
                                 1, window,
                                 0, direction, delta, 0);
    successful = true;
  }
  catch (ex) {
    successful = false;
  }
  ok(successful, "event.initSimpleGestureEvent should not fail");

  // Make sure the event fields match the expected values
  is(event.type, type, "Mismatch on evt.type");
  is(event.direction, direction, "Mismatch on evt.direction");
  is(event.delta, delta, "Mismatch on evt.delta");
  is(event.altKey, altKey, "Mismatch on evt.altKey");
  is(event.ctrlKey, ctrlKey, "Mismatch on evt.ctrlKey");
  is(event.shiftKey, shiftKey, "Mismatch on evt.shiftKey");
  is(event.metaKey, metaKey, "Mismatch on evt.metaKey");
  is(event.view, window, "Mismatch on evt.view");
  is(event.detail, 1, "Mismatch on evt.detail");
  is(event.clientX, 10, "Mismatch on evt.clientX");
  is(event.clientY, 10, "Mismatch on evt.clientY");
  is(event.screenX, 10, "Mismatch on evt.screenX");
  is(event.screenY, 10, "Mismatch on evt.screenY");
  is(event.button, 1, "Mismatch on evt.button");
  is(event.relatedTarget, window, "Mismatch on evt.relatedTarget");

  // Test event dispatch
  let expectedEventCount = test_eventCount + 1;
  document.addEventListener(type, test_eventDispatchListener, true);
  document.dispatchEvent(event);
  document.removeEventListener(type, test_eventDispatchListener, true);
  is(expectedEventCount, test_eventCount, "Dispatched event was never received by listener");
}

function test_TestEventCreation()
{
  // Event creation
  test_helper2("MozMagnifyGesture", SimpleGestureEvent.DIRECTION_RIGHT, 20.0,
               true, false, true, false);
  test_helper2("MozMagnifyGesture", SimpleGestureEvent.DIRECTION_LEFT, -20.0,
               false, true, false, true);
}

function test_EnsureConstantsAreDisjoint()
{
  let up = SimpleGestureEvent.DIRECTION_UP;
  let down = SimpleGestureEvent.DIRECTION_DOWN;
  let left = SimpleGestureEvent.DIRECTION_LEFT;
  let right = SimpleGestureEvent.DIRECTION_RIGHT;

  let clockwise = SimpleGestureEvent.ROTATION_CLOCKWISE;
  let cclockwise = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE;

  ok(up ^ down, "DIRECTION_UP and DIRECTION_DOWN are not bitwise disjoint");
  ok(up ^ left, "DIRECTION_UP and DIRECTION_LEFT are not bitwise disjoint");
  ok(up ^ right, "DIRECTION_UP and DIRECTION_RIGHT are not bitwise disjoint");
  ok(down ^ left, "DIRECTION_DOWN and DIRECTION_LEFT are not bitwise disjoint");
  ok(down ^ right, "DIRECTION_DOWN and DIRECTION_RIGHT are not bitwise disjoint");
  ok(left ^ right, "DIRECTION_LEFT and DIRECTION_RIGHT are not bitwise disjoint");
  ok(clockwise ^ cclockwise, "ROTATION_CLOCKWISE and ROTATION_COUNTERCLOCKWISE are not bitwise disjoint");
}

// Helper for test of latched event processing. Emits the actual
// gesture events to test whether the commands associated with the
// gesture will only trigger once for each direction of movement.
function test_emitLatchedEvents(eventPrefix, initialDelta, cmd)
{
  let cumulativeDelta = 0;
  let isIncreasing = initialDelta > 0;

  let expect = {};
  // Reset the call counters and initialize expected values
  for (let dir in cmd)
    cmd[dir].callCount = expect[dir] = 0;

  let check = (aDir, aMsg) => ok(cmd[aDir].callCount == expect[aDir], aMsg);
  let checkBoth = function(aNum, aInc, aDec) {
    let prefix = "Step " + aNum + ": ";
    check("inc", prefix + aInc);
    check("dec", prefix + aDec);
  };

  // Send the "Start" event.
  test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 0, 0, initialDelta, 0);
  cumulativeDelta += initialDelta;
  if (isIncreasing) {
    expect.inc++;
    checkBoth(1, "Increasing command was not triggered", "Decreasing command was triggered");
  } else {
    expect.dec++;
    checkBoth(1, "Increasing command was triggered", "Decreasing command was not triggered");
  }

  // Send random values in the same direction and ensure neither
  // command triggers.
  for (let i = 0; i < 5; i++) {
      let delta = Math.random() * (isIncreasing ? 100 : -100);
    test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, delta, 0);
    cumulativeDelta += delta;
    checkBoth(2, "Increasing command was triggered", "Decreasing command was triggered");
  }

  // Now go back in the opposite direction.
  test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0,
				    - initialDelta, 0);
  cumulativeDelta += - initialDelta;
  if (isIncreasing) {
    expect.dec++;
    checkBoth(3, "Increasing command was triggered", "Decreasing command was not triggered");
  } else {
    expect.inc++;
    checkBoth(3, "Increasing command was not triggered", "Decreasing command was triggered");
  }

  // Send random values in the opposite direction and ensure neither
  // command triggers.
  for (let i = 0; i < 5; i++) {
    let delta = Math.random() * (isIncreasing ? -100 : 100);
    test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, delta, 0);
    cumulativeDelta += delta;
    checkBoth(4, "Increasing command was triggered", "Decreasing command was triggered");
  }

  // Go back to the original direction. The original command should trigger.
  test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0,
				    initialDelta, 0);
  cumulativeDelta += initialDelta;
  if (isIncreasing) {
    expect.inc++;
    checkBoth(5, "Increasing command was not triggered", "Decreasing command was triggered");
  } else {
    expect.dec++;
    checkBoth(5, "Increasing command was triggered", "Decreasing command was not triggered");
  }

  // Send the wrap-up event. No commands should be triggered.
  test_utils.sendSimpleGestureEvent(eventPrefix, 0, 0, 0, cumulativeDelta, 0);
  checkBoth(6, "Increasing command was triggered", "Decreasing command was triggered");
}

function test_addCommand(prefName, id)
{
  let cmd = test_commandset.appendChild(document.createElement("command"));
  cmd.setAttribute("id", id);
  cmd.setAttribute("oncommand", "this.callCount++;");

  cmd.origPrefName = prefName;
  cmd.origPrefValue = gPrefService.getCharPref(prefName);
  gPrefService.setCharPref(prefName, id);

  return cmd;
}

function test_removeCommand(cmd)
{
  gPrefService.setCharPref(cmd.origPrefName, cmd.origPrefValue);
  test_commandset.removeChild(cmd);
}

// Test whether latched events are only called once per direction of motion.
function test_latchedGesture(gesture, inc, dec, eventPrefix)
{
  let branch = test_prefBranch + gesture + ".";

  // Put the gesture into latched mode.
  let oldLatchedValue = gPrefService.getBoolPref(branch + "latched");
  gPrefService.setBoolPref(branch + "latched", true);

  // Install the test commands for increasing and decreasing motion.
  let cmd = {
    inc: test_addCommand(branch + inc, "test:incMotion"),
    dec: test_addCommand(branch + dec, "test:decMotion"),
  };

  // Test the gestures in each direction.
  test_emitLatchedEvents(eventPrefix, 500, cmd);
  test_emitLatchedEvents(eventPrefix, -500, cmd);

  // Restore the gesture to its original configuration.
  gPrefService.setBoolPref(branch + "latched", oldLatchedValue);
  for (let dir in cmd)
    test_removeCommand(cmd[dir]);
}

// Test whether non-latched events are triggered upon sufficient motion.
function test_thresholdGesture(gesture, inc, dec, eventPrefix)
{
  let branch = test_prefBranch + gesture + ".";

  // Disable latched mode for this gesture.
  let oldLatchedValue = gPrefService.getBoolPref(branch + "latched");
  gPrefService.setBoolPref(branch + "latched", false);

  // Set the triggering threshold value to 50.
  let oldThresholdValue = gPrefService.getIntPref(branch + "threshold");
  gPrefService.setIntPref(branch + "threshold", 50);

  // Install the test commands for increasing and decreasing motion.
  let cmdInc = test_addCommand(branch + inc, "test:incMotion");
  let cmdDec = test_addCommand(branch + dec, "test:decMotion");

  // Send the start event but stop short of triggering threshold.
  cmdInc.callCount = cmdDec.callCount = 0;
  test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 0, 0, 49.5, 0);
  ok(cmdInc.callCount == 0, "Increasing command was triggered");
  ok(cmdDec.callCount == 0, "Decreasing command was triggered");

  // Now trigger the threshold.
  cmdInc.callCount = cmdDec.callCount = 0;
  test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, 1, 0);
  ok(cmdInc.callCount == 1, "Increasing command was not triggered");
  ok(cmdDec.callCount == 0, "Decreasing command was triggered");

  // The tracking counter should go to zero. Go back the other way and
  // stop short of triggering the threshold.
  cmdInc.callCount = cmdDec.callCount = 0;
  test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, -49.5, 0);
  ok(cmdInc.callCount == 0, "Increasing command was triggered");
  ok(cmdDec.callCount == 0, "Decreasing command was triggered");

  // Now cross the threshold and trigger the decreasing command.
  cmdInc.callCount = cmdDec.callCount = 0;
  test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, -1.5, 0);
  ok(cmdInc.callCount == 0, "Increasing command was triggered");
  ok(cmdDec.callCount == 1, "Decreasing command was not triggered");

  // Send the wrap-up event. No commands should trigger.
  cmdInc.callCount = cmdDec.callCount = 0;
  test_utils.sendSimpleGestureEvent(eventPrefix, 0, 0, 0, -0.5, 0);
  ok(cmdInc.callCount == 0, "Increasing command was triggered");
  ok(cmdDec.callCount == 0, "Decreasing command was triggered");

  // Restore the gesture to its original configuration.
  gPrefService.setBoolPref(branch + "latched", oldLatchedValue);
  gPrefService.setIntPref(branch + "threshold", oldThresholdValue);
  test_removeCommand(cmdInc);
  test_removeCommand(cmdDec);
}

function test_swipeGestures()
{
  // easier to type names for the direction constants
  let up = SimpleGestureEvent.DIRECTION_UP;
  let down = SimpleGestureEvent.DIRECTION_DOWN;
  let left = SimpleGestureEvent.DIRECTION_LEFT;
  let right = SimpleGestureEvent.DIRECTION_RIGHT;

  let branch = test_prefBranch + "swipe.";

  // Install the test commands for the swipe gestures.
  let cmdUp = test_addCommand(branch + "up", "test:swipeUp");
  let cmdDown = test_addCommand(branch + "down", "test:swipeDown");
  let cmdLeft = test_addCommand(branch + "left", "test:swipeLeft");
  let cmdRight = test_addCommand(branch + "right", "test:swipeRight");

  function resetCounts() {
    cmdUp.callCount = 0;
    cmdDown.callCount = 0;
    cmdLeft.callCount = 0;
    cmdRight.callCount = 0;
  }

  // UP
  resetCounts();
  test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, up, 0, 0);
  ok(cmdUp.callCount == 1, "Step 1: Up command was not triggered");
  ok(cmdDown.callCount == 0, "Step 1: Down command was triggered");
  ok(cmdLeft.callCount == 0, "Step 1: Left command was triggered");
  ok(cmdRight.callCount == 0, "Step 1: Right command was triggered");

  // DOWN
  resetCounts();
  test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, down, 0, 0);
  ok(cmdUp.callCount == 0, "Step 2: Up command was triggered");
  ok(cmdDown.callCount == 1, "Step 2: Down command was not triggered");
  ok(cmdLeft.callCount == 0, "Step 2: Left command was triggered");
  ok(cmdRight.callCount == 0, "Step 2: Right command was triggered");

  // LEFT
  resetCounts();
  test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, left, 0, 0);
  ok(cmdUp.callCount == 0, "Step 3: Up command was triggered");
  ok(cmdDown.callCount == 0, "Step 3: Down command was triggered");
  ok(cmdLeft.callCount == 1, "Step 3: Left command was not triggered");
  ok(cmdRight.callCount == 0, "Step 3: Right command was triggered");

  // RIGHT
  resetCounts();
  test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, right, 0, 0);
  ok(cmdUp.callCount == 0, "Step 4: Up command was triggered");
  ok(cmdDown.callCount == 0, "Step 4: Down command was triggered");
  ok(cmdLeft.callCount == 0, "Step 4: Left command was triggered");
  ok(cmdRight.callCount == 1, "Step 4: Right command was not triggered");

  // Make sure combinations do not trigger events.
  let combos = [ up | left, up | right, down | left, down | right];
  for (let i = 0; i < combos.length; i++) {
    resetCounts();
    test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, combos[i], 0, 0);
    ok(cmdUp.callCount == 0, "Step 5-"+i+": Up command was triggered");
    ok(cmdDown.callCount == 0, "Step 5-"+i+": Down command was triggered");
    ok(cmdLeft.callCount == 0, "Step 5-"+i+": Left command was triggered");
    ok(cmdRight.callCount == 0, "Step 5-"+i+": Right command was triggered");
  }

  // Remove the test commands.
  test_removeCommand(cmdUp);
  test_removeCommand(cmdDown);
  test_removeCommand(cmdLeft);
  test_removeCommand(cmdRight);
}


function test_rotateHelperGetImageRotation(aImageElement)
{
  // Get the true image rotation from the transform matrix, bounded
  // to 0 <= result < 360
  let transformValue = content.window.getComputedStyle(aImageElement, null)
                                     .transform;
  if (transformValue == "none")
    return 0;

  transformValue = transformValue.split("(")[1]
                                 .split(")")[0]
                                 .split(",");
  var rotation = Math.round(Math.atan2(transformValue[1], transformValue[0]) *
                            (180 / Math.PI));
  return (rotation < 0 ? rotation + 360 : rotation);
}

function test_rotateHelperOneGesture(aImageElement, aCurrentRotation,
                                     aDirection, aAmount, aStop)
{
  if (aAmount <= 0 || aAmount > 90) // Bound to 0 < aAmount <= 90
    return;

  // easier to type names for the direction constants
  let clockwise = SimpleGestureEvent.ROTATION_CLOCKWISE;

  let delta = aAmount * (aDirection == clockwise ? 1 : -1);

  // Kill transition time on image so test isn't wrong and doesn't take 10 seconds
  aImageElement.style.transitionDuration = "0s";

  // Start the gesture, perform an update, and force flush
  test_utils.sendSimpleGestureEvent("MozRotateGestureStart", 0, 0, aDirection, .001, 0);
  test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, aDirection, delta, 0);
  aImageElement.clientTop;

  // If stop, check intermediate
  if (aStop) {
    // Send near-zero-delta to stop, and force flush
    test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, aDirection, .001, 0);
    aImageElement.clientTop;

    let stopExpectedRotation = (aCurrentRotation + delta) % 360;
    if (stopExpectedRotation < 0)
      stopExpectedRotation += 360;

    is(stopExpectedRotation, test_rotateHelperGetImageRotation(aImageElement),
       "Image rotation at gesture stop/hold: expected=" + stopExpectedRotation +
       ", observed=" + test_rotateHelperGetImageRotation(aImageElement) +
       ", init=" + aCurrentRotation +
       ", amt=" + aAmount +
       ", dir=" + (aDirection == clockwise ? "cl" : "ccl"));
  }
  // End it and force flush
  test_utils.sendSimpleGestureEvent("MozRotateGesture", 0, 0, aDirection, 0, 0);
  aImageElement.clientTop;

  let finalExpectedRotation;

  if (aAmount < 45 && aStop) {
    // Rotate a bit, then stop.  Expect no change at end of gesture.
    finalExpectedRotation = aCurrentRotation;
  }
  else {
    // Either not stopping (expect 90 degree change in aDirection), OR
    // stopping but after 45, (expect 90 degree change in aDirection)
    finalExpectedRotation = (aCurrentRotation +
                             (aDirection == clockwise ? 1 : -1) * 90) % 360;
    if (finalExpectedRotation < 0)
      finalExpectedRotation += 360;
  }

  is(finalExpectedRotation, test_rotateHelperGetImageRotation(aImageElement),
     "Image rotation gesture end: expected=" + finalExpectedRotation +
     ", observed=" + test_rotateHelperGetImageRotation(aImageElement) +
     ", init=" + aCurrentRotation +
     ", amt=" + aAmount +
     ", dir=" + (aDirection == clockwise ? "cl" : "ccl"));
}

function test_rotateGesturesOnTab()
{
  gBrowser.selectedBrowser.removeEventListener("load", test_rotateGesturesOnTab, true);

  if (!(content.document instanceof ImageDocument)) {
    ok(false, "Image document failed to open for rotation testing");
    gBrowser.removeTab(test_imageTab);
    finish();
    return;
  }

  // easier to type names for the direction constants
  let cl = SimpleGestureEvent.ROTATION_CLOCKWISE;
  let ccl = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE;

  let imgElem = content.document.body &&
                content.document.body.firstElementChild;

  if (!imgElem) {
    ok(false, "Could not get image element on ImageDocument for rotation!");
    gBrowser.removeTab(test_imageTab);
    finish();
    return;
  }

  // Quick function to normalize rotation to 0 <= r < 360
  var normRot = function(rotation) {
    rotation = rotation % 360;
    if (rotation < 0)
      rotation += 360;
    return rotation;
  }

  for (var initRot = 0; initRot < 360; initRot += 90) {
    // Test each case: at each 90 degree snap; cl/ccl;
    // amount more or less than 45; stop and hold or don't (32 total tests)
    // The amount added to the initRot is where it is expected to be
    test_rotateHelperOneGesture(imgElem, normRot(initRot +   0), cl,  35, true );
    test_rotateHelperOneGesture(imgElem, normRot(initRot +   0), cl,  35, false);
    test_rotateHelperOneGesture(imgElem, normRot(initRot +  90), cl,  55, true );
    test_rotateHelperOneGesture(imgElem, normRot(initRot + 180), cl,  55, false);
    test_rotateHelperOneGesture(imgElem, normRot(initRot + 270), ccl, 35, true );
    test_rotateHelperOneGesture(imgElem, normRot(initRot + 270), ccl, 35, false);
    test_rotateHelperOneGesture(imgElem, normRot(initRot + 180), ccl, 55, true );
    test_rotateHelperOneGesture(imgElem, normRot(initRot +  90), ccl, 55, false);

    // Manually rotate it 90 degrees clockwise to prepare for next iteration,
    // and force flush
    test_utils.sendSimpleGestureEvent("MozRotateGestureStart", 0, 0, cl, .001, 0);
    test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, cl, 90, 0);
    test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, cl, .001, 0);
    test_utils.sendSimpleGestureEvent("MozRotateGesture", 0, 0, cl, 0, 0);
    imgElem.clientTop;
  }

  gBrowser.removeTab(test_imageTab);
  test_imageTab = null;
  finish();
}

function test_rotateGestures()
{
  test_imageTab = gBrowser.addTab("chrome://branding/content/about-logo.png");
  gBrowser.selectedTab = test_imageTab;

  gBrowser.selectedBrowser.addEventListener("load", test_rotateGesturesOnTab, true);
}