Bug 599382 - [TEST] Add some browser-chrome test for tapping [r=mbrubeck]
authorMark Finkle <mfinkle@mozilla.com>
Tue, 28 Sep 2010 17:15:58 -0400
changeset 66723 b2197dd39de4270920e58a544bf4de9b9cbaa131
parent 66722 c9320434944814dd5421d5bbdb01eb61ad365e36
child 66724 d7bb3a243cca52853beaba29d0de1f3c9bc51072
push idunknown
push userunknown
push dateunknown
reviewersmbrubeck
bugs599382
Bug 599382 - [TEST] Add some browser-chrome test for tapping [r=mbrubeck]
mobile/chrome/tests/Makefile.in
mobile/chrome/tests/browser_tap_content.html
mobile/chrome/tests/browser_tapping.js
mobile/chrome/tests/head.js
--- a/mobile/chrome/tests/Makefile.in
+++ b/mobile/chrome/tests/Makefile.in
@@ -63,16 +63,18 @@ include $(topsrcdir)/config/rules.mk
   browser_navigation.js \
   browser_preferences_basic.js \
   browser_preferences_text.js \
   browser_rect.js \
   browser_select.html \
   browser_select.js \
   browser_sessionstore.js \
   browser_tabs.js \
+  browser_tapping.js \
+  browser_tap_content.html \
   browser_viewport_00.html \
   browser_viewport_01.html \
   browser_viewport_02.html \
   browser_viewport_03.html \
   browser_viewport_04.html \
   browser_viewport_05.html \
   browser_viewport_06.html \
   browser_viewport_07.html \
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/tests/browser_tap_content.html
@@ -0,0 +1,22 @@
+<html style="border: 1px solid red">
+<title>Browser Tap Page 01</title>
+<body>
+  <p style="margin-bottom:100px">Here are some images and links. We use them to test context menu (long taps)</p>
+
+  <div style="margin-bottom:20px">
+    <div>A plain link</div>
+    <a id="link-single" href="browser_blank_01.html">A blank page - nothing interesting</a>
+  </div>
+
+  <div style="margin-bottom:20px">
+    <div>A plain image</div>
+    <img id="img-single" src="data:image/jpeg,%FF%D8%FF%E0%00%10JFIF%00%01%01%00%00%01%00%01%00%00%FF%FE%00%3BCREATOR%3A%20gd-jpeg%20v1.0%20(using%20IJG%20JPEG%20v62)%2C%20quality%20%3D%2090%0A%FF%DB%00C%00%03%02%02%03%02%02%03%03%03%03%04%03%03%04%05%08%05%05%04%04%05%0A%07%07%06%08%0C%0A%0C%0C%0B%0A%0B%0B%0D%0E%12%10%0D%0E%11%0E%0B%0B%10%16%10%11%13%14%15%15%15%0C%0F%17%18%16%14%18%12%14%15%14%FF%DB%00C%01%03%04%04%05%04%05%09%05%05%09%14%0D%0B%0D%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%FF%C0%00%11%08%00%24%00%24%03%01%22%00%02%11%01%03%11%01%FF%C4%00%1F%00%00%01%05%01%01%01%01%01%01%00%00%00%00%00%00%00%00%01%02%03%04%05%06%07%08%09%0A%0B%FF%C4%00%B5%10%00%02%01%03%03%02%04%03%05%05%04%04%00%00%01%7D%01%02%03%00%04%11%05%12!1A%06%13Qa%07%22q%142%81%91%A1%08%23B%B1%C1%15R%D1%F0%243br%82%09%0A%16%17%18%19%1A%25%26'()*456789%3ACDEFGHIJSTUVWXYZcdefghijstuvwxyz%83%84%85%86%87%88%89%8A%92%93%94%95%96%97%98%99%9A%A2%A3%A4%A5%A6%A7%A8%A9%AA%B2%B3%B4%B5%B6%B7%B8%B9%BA%C2%C3%C4%C5%C6%C7%C8%C9%CA%D2%D3%D4%D5%D6%D7%D8%D9%DA%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FF%C4%00%1F%01%00%03%01%01%01%01%01%01%01%01%01%00%00%00%00%00%00%01%02%03%04%05%06%07%08%09%0A%0B%FF%C4%00%B5%11%00%02%01%02%04%04%03%04%07%05%04%04%00%01%02w%00%01%02%03%11%04%05!1%06%12AQ%07aq%13%222%81%08%14B%91%A1%B1%C1%09%233R%F0%15br%D1%0A%16%244%E1%25%F1%17%18%19%1A%26'()*56789%3ACDEFGHIJSTUVWXYZcdefghijstuvwxyz%82%83%84%85%86%87%88%89%8A%92%93%94%95%96%97%98%99%9A%A2%A3%A4%A5%A6%A7%A8%A9%AA%B2%B3%B4%B5%B6%B7%B8%B9%BA%C2%C3%C4%C5%C6%C7%C8%C9%CA%D2%D3%D4%D5%D6%D7%D8%D9%DA%E2%E3%E4%E5%E6%E7%E8%E9%EA%F2%F3%F4%F5%F6%F7%F8%F9%FA%FF%DA%00%0C%03%01%00%02%11%03%11%00%3F%00%F8%D7C%F1%5CW%DF%194%FDf%F2%F5%26%8AR%3C%EB%82%C0%8D%BEQBI%1E%82%B8%3F%18%7Cm%F1G%8D4%3D%0FA%7DFX%F4%AD%26%D1--%AC%E0%FD%DCe%23%18V%60%3E%F3%11%D4%9C%FB%60W%23gos96%D6%EE%242%F0%10(%04%E7%AFJ%F4%CF%82%FF%00%0E%2C%EF%ED%A5%D7%B5%E8%5C%D8A!%89a%11%92X%8E%A7%03%9E%BC~u%CDR%AA%A6%A5%2B%EEm%87%C2%FBNXv%EAs%FE%11%97UeYD%97%11%A5%AF%EFCE%2F%20%8E%A7o%F9%F5%CDz%BF%C3%BF%19x%82%EFH%D5%B4W%D6%AF%5E%CE%FA%DEKYl%24%95%DA%22%0Cm%8F%90%F4%25%D5%08%E3%A9%1D%06k%E9_%82%DE%12%F0%07%8B%2FF%9D%1Cztf%E16%B5%B4%F6%C6%19%24_%E2%03p%04%FE%15%DA%FCN%FD%9Ac%F8u%E1%3F%F8O%7C6%B6%D2%D9i%12G%F6%8D8B%04%9ECH%A1%B0%F9%C9%23%3Cw8%EB%9CW%241R%95%EF%1B%1E%BDL%BE%10%8D%F9%93%3F9%E6%07%7D%15%B9%E3%98l%A0%F1v%AA%9Al%82%5B%0F%B49%85%80%C7%C8I%20c%DB8%FC(%AD%13%3C%87%B9%E5%BE%0F%BEXu%FBS2%3C%88K)X%FA%8C%A9%19%E7%D39%3FJ%FB%7F%E0%D7%86t%FF%00%EC(%F4%B9-%BC%EB)%8E%D7%8C%9E%08''%3F%9Dx%DF%80%BFf%1DCM%D2%AF%B5%3Df%16%93X6%EEl%AD%20%C9T%90%00%40%24%03%B9%9B%EE%81%C7%5E%F9%06%BD%F3%E1%95%FC~%1E%B0Qp%85X.%E5c%FC%40%8E%08%AE%5C%7BM%FB%A7%BD%94%C9E%3Eu%A7%F5%FEGu%F0%FB%C1%DAF%97%F1%9F%C3%9A%0E%93%17%92%24%93%CB%91%22F1%C4%AC%09%CEO%1B%B9%CE%075%2F%ED%19%F0%F7Q%F89%FB6%F8%A7Yo%1E%EA%1A%D0%D6o%ED%AC~%CE%D2%E2%19%B3%2F%9B%B8%A7M%C0!%E0%01%D0%F2Ev_%01%FC%23%AB%5E%F8%9AMJ7%B2%BD%D8%AC%EBq%3C%04%B2%12%A7%03%EF%83%DCr%2B%D7~%23~%C6Z%1F%C7%DF%84%9A6%87%E2%3DwV%D1%A6%B6%9D%EF%ED%24%D3%E4O)e*P%19Q%94%EF%00%16%C0%05N%09%E7%9A%8C579-7%3As%19%D3%A5I%A4%ED%D9z%9F%8C%D2j%11%B3d%9C%9CQ%5E%FB%F1W%FE%09%F5%F1g%E1%EF%8D.%F4m%2FMO%14%D8D%03%C3%A9Z%C9%1C%0B%22%9E%99I%1C%10%DFBG%3DO8%2B%D7%F62%EC%7C%97%3C%7B%9FaO%E1%ABM%0Ak1%03%CE%E5%23K%80e%94%9C%BE%C9rp09%0A%01%E3%D7%BF57%85%BC%0D%A3x%85%AE%A0%BC%B3R%8B%24%C1%0ApSl%AE%9C%7DB%82%7D%C94Q%5EF%23%E1O%CD%1E%E6%0B%E3k%C9%9A%3A%C6%9B%FF%00%08%3C%1E%1C%8FG%BB%B9%B4%FE%D2%D5%23%B5%9D%96N%7C%A1%C9Q%C7%19%C6%0F%B5%7Dw%F0%F6%EEK%EF%0CX%19NY-%89W%EA%D8%12%B2%81%93%DB%0A%3D%FA%D1Ew%60~%26c%99%7C%11%13%C6%96%16%B7%1A%ACm5%B430%85%40iP1%C6OsE%14W%ABvxvG%FF%D9"/>
+  </div>
+
+  <div style="margin-bottom:20px">
+    <div>A nested image inside a link</div>
+    <a id="link-nested" href="browser_blank_02.html"><img id="img-nested" src="data:image/jpeg,%FF%D8%FF%E0%00%10JFIF%00%01%01%00%00%01%00%01%00%00%FF%FE%00%3BCREATOR%3A%20gd-jpeg%20v1.0%20(using%20IJG%20JPEG%20v62)%2C%20quality%20%3D%2090%0A%FF%DB%00C%00%03%02%02%03%02%02%03%03%03%03%04%03%03%04%05%08%05%05%04%04%05%0A%07%07%06%08%0C%0A%0C%0C%0B%0A%0B%0B%0D%0E%12%10%0D%0E%11%0E%0B%0B%10%16%10%11%13%14%15%15%15%0C%0F%17%18%16%14%18%12%14%15%14%FF%DB%00C%01%03%04%04%05%04%05%09%05%05%09%14%0D%0B%0D%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%14%FF%C0%00%11%08%00%24%00%24%03%01%22%00%02%11%01%03%11%01%FF%C4%00%1F%00%00%01%05%01%01%01%01%01%01%00%00%00%00%00%00%00%00%01%02%03%04%05%06%07%08%09%0A%0B%FF%C4%00%B5%10%00%02%01%03%03%02%04%03%05%05%04%04%00%00%01%7D%01%02%03%00%04%11%05%12!1A%06%13Qa%07%22q%142%81%91%A1%08%23B%B1%C1%15R%D1%F0%243br%82%09%0A%16%17%18%19%1A%25%26'()*456789%3ACDEFGHIJSTUVWXYZcdefghijstuvwxyz%83%84%85%86%87%88%89%8A%92%93%94%95%96%97%98%99%9A%A2%A3%A4%A5%A6%A7%A8%A9%AA%B2%B3%B4%B5%B6%B7%B8%B9%BA%C2%C3%C4%C5%C6%C7%C8%C9%CA%D2%D3%D4%D5%D6%D7%D8%D9%DA%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FF%C4%00%1F%01%00%03%01%01%01%01%01%01%01%01%01%00%00%00%00%00%00%01%02%03%04%05%06%07%08%09%0A%0B%FF%C4%00%B5%11%00%02%01%02%04%04%03%04%07%05%04%04%00%01%02w%00%01%02%03%11%04%05!1%06%12AQ%07aq%13%222%81%08%14B%91%A1%B1%C1%09%233R%F0%15br%D1%0A%16%244%E1%25%F1%17%18%19%1A%26'()*56789%3ACDEFGHIJSTUVWXYZcdefghijstuvwxyz%82%83%84%85%86%87%88%89%8A%92%93%94%95%96%97%98%99%9A%A2%A3%A4%A5%A6%A7%A8%A9%AA%B2%B3%B4%B5%B6%B7%B8%B9%BA%C2%C3%C4%C5%C6%C7%C8%C9%CA%D2%D3%D4%D5%D6%D7%D8%D9%DA%E2%E3%E4%E5%E6%E7%E8%E9%EA%F2%F3%F4%F5%F6%F7%F8%F9%FA%FF%DA%00%0C%03%01%00%02%11%03%11%00%3F%00%F8%D7C%F1%5CW%DF%194%FDf%F2%F5%26%8AR%3C%EB%82%C0%8D%BEQBI%1E%82%B8%3F%18%7Cm%F1G%8D4%3D%0FA%7DFX%F4%AD%26%D1--%AC%E0%FD%DCe%23%18V%60%3E%F3%11%D4%9C%FB%60W%23gos96%D6%EE%242%F0%10(%04%E7%AFJ%F4%CF%82%FF%00%0E%2C%EF%ED%A5%D7%B5%E8%5C%D8A!%89a%11%92X%8E%A7%03%9E%BC~u%CDR%AA%A6%A5%2B%EEm%87%C2%FBNXv%EAs%FE%11%97UeYD%97%11%A5%AF%EFCE%2F%20%8E%A7o%F9%F5%CDz%BF%C3%BF%19x%82%EFH%D5%B4W%D6%AF%5E%CE%FA%DEKYl%24%95%DA%22%0Cm%8F%90%F4%25%D5%08%E3%A9%1D%06k%E9_%82%DE%12%F0%07%8B%2FF%9D%1Cztf%E16%B5%B4%F6%C6%19%24_%E2%03p%04%FE%15%DA%FCN%FD%9Ac%F8u%E1%3F%F8O%7C6%B6%D2%D9i%12G%F6%8D8B%04%9ECH%A1%B0%F9%C9%23%3Cw8%EB%9CW%241R%95%EF%1B%1E%BDL%BE%10%8D%F9%93%3F9%E6%07%7D%15%B9%E3%98l%A0%F1v%AA%9Al%82%5B%0F%B49%85%80%C7%C8I%20c%DB8%FC(%AD%13%3C%87%B9%E5%BE%0F%BEXu%FBS2%3C%88K)X%FA%8C%A9%19%E7%D39%3FJ%FB%7F%E0%D7%86t%FF%00%EC(%F4%B9-%BC%EB)%8E%D7%8C%9E%08''%3F%9Dx%DF%80%BFf%1DCM%D2%AF%B5%3Df%16%93X6%EEl%AD%20%C9T%90%00%40%24%03%B9%9B%EE%81%C7%5E%F9%06%BD%F3%E1%95%FC~%1E%B0Qp%85X.%E5c%FC%40%8E%08%AE%5C%7BM%FB%A7%BD%94%C9E%3Eu%A7%F5%FEGu%F0%FB%C1%DAF%97%F1%9F%C3%9A%0E%93%17%92%24%93%CB%91%22F1%C4%AC%09%CEO%1B%B9%CE%075%2F%ED%19%F0%F7Q%F89%FB6%F8%A7Yo%1E%EA%1A%D0%D6o%ED%AC~%CE%D2%E2%19%B3%2F%9B%B8%A7M%C0!%E0%01%D0%F2Ev_%01%FC%23%AB%5E%F8%9AMJ7%B2%BD%D8%AC%EBq%3C%04%B2%12%A7%03%EF%83%DCr%2B%D7~%23~%C6Z%1F%C7%DF%84%9A6%87%E2%3DwV%D1%A6%B6%9D%EF%ED%24%D3%E4O)e*P%19Q%94%EF%00%16%C0%05N%09%E7%9A%8C579-7%3As%19%D3%A5I%A4%ED%D9z%9F%8C%D2j%11%B3d%9C%9CQ%5E%FB%F1W%FE%09%F5%F1g%E1%EF%8D.%F4m%2FMO%14%D8D%03%C3%A9Z%C9%1C%0B%22%9E%99I%1C%10%DFBG%3DO8%2B%D7%F62%EC%7C%97%3C%7B%9FaO%E1%ABM%0Ak1%03%CE%E5%23K%80e%94%9C%BE%C9rp09%0A%01%E3%D7%BF57%85%BC%0D%A3x%85%AE%A0%BC%B3R%8B%24%C1%0ApSl%AE%9C%7DB%82%7D%C94Q%5EF%23%E1O%CD%1E%E6%0B%E3k%C9%9A%3A%C6%9B%FF%00%08%3C%1E%1C%8FG%BB%B9%B4%FE%D2%D5%23%B5%9D%96N%7C%A1%C9Q%C7%19%C6%0F%B5%7Dw%F0%F6%EEK%EF%0CX%19NY-%89W%EA%D8%12%B2%81%93%DB%0A%3D%FA%D1Ew%60~%26c%99%7C%11%13%C6%96%16%B7%1A%ACm5%B430%85%40iP1%C6OsE%14W%ABvxvG%FF%D9"/></a>
+  </div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/tests/browser_tapping.js
@@ -0,0 +1,276 @@
+/*
+ * Testing the tapping interactions:
+ *   single tap, double tap & long tap
+ */
+
+let testURL = "chrome://mochikit/content/browser/mobile/chrome/browser_tap_content.html";
+
+let gTests = [];
+let gCurrentTest = null;
+let gCurrentTab;
+
+let gEvents = [];
+function dumpEvents(aEvent) {
+  gEvents.push(aEvent.type);
+}
+
+function clearEvents() {
+  gEvents = [];
+}
+
+function checkEvents(aEvents) {
+  if (aEvents.length != gEvents.length) {
+    dump("---- event check: failed length (" + aEvents.length + " != " + gEvents.length + ")\n");
+    dump("---- expected: [" + aEvents.join(",") + "] actual: [" + gEvents.join(",") + "]\n");
+    return false;
+  }
+
+  for (let i=0; i<aEvents.length; i++) {
+    if (aEvents[i] != gEvents[i]) {
+      dump("---- event check: failed match (" + aEvents[i] + " != " + gEvents[i] + "\n");
+      return false;
+    }
+  }
+  return true;
+}
+
+let gContextTypes = "";
+function dumpMessages(aMessage) {
+  if (aMessage.name == "Browser:ContextMenu") {
+    aMessage.json.types.forEach(function(aType) {
+      gContextTypes.push(aType);
+    })
+  }  
+}
+
+function clearContextTypes() {
+  gContextTypes = [];
+
+  if (ContextHelper.popupState)
+    ContextHelper.hide();
+}
+
+function checkContextTypes(aTypes) {
+  if (aTypes.length != gContextTypes.length) {
+    dump("---- type check: failed length (" + aTypes.length + " != " + gContextTypes.length + ")\n");
+    dump("---- expected: [" + aTypes.join(",") + "] actual: [" + gContextTypes.join(",") + "]\n");
+    return false;
+  }
+
+  for (let i=0; i<aTypes.length; i++) {
+    if (gContextTypes.indexOf(aTypes[i]) == -1) {
+      dump("---- type check: failed match (" + aTypes[i] + ")\n");
+      dump("---- expected: [" + aTypes.join(",") + "] actual: [" + gContextTypes.join(",") + "]\n");
+      return false;
+    }
+  }
+  return true;
+}
+
+
+function test() {
+  // The "runNextTest" approach is async, so we need to call "waitForExplicitFinish()"
+  // We call "finish()" when the tests are finished
+  waitForExplicitFinish();
+
+  // Add new tab
+  gCurrentTab = Browser.addTab(testURL, true);
+  ok(gCurrentTab, "Tab Opened");
+
+  window.addEventListener("TapSingle", dumpEvents, true);
+  window.addEventListener("TapDouble", dumpEvents, true);
+  window.addEventListener("TapLong", dumpEvents, true);
+  
+  // Wait for the tab to load, then do the tests
+  messageManager.addMessageListener("pageshow", function() {
+  if (gCurrentTab.browser.currentURI.spec == testURL) {
+    messageManager.removeMessageListener("pageshow", arguments.callee);
+    runNextTest();
+  }});
+}
+
+//------------------------------------------------------------------------------
+// Iterating tests by shifting test out one by one as runNextTest is called.
+function runNextTest() {
+  // Run the next test until all tests completed
+  if (gTests.length > 0) {
+    gCurrentTest = gTests.shift();
+    info(gCurrentTest.desc);
+    gCurrentTest.run();
+  }
+  else {
+    window.removeEventListener("TapSingle", dumpEvents, true);
+    window.removeEventListener("TapDouble", dumpEvents, true);
+    window.removeEventListener("TapLong", dumpEvents, true);
+
+    Browser.closeTab(gCurrentTab);
+
+    finish();
+  }
+}
+
+//------------------------------------------------------------------------------
+// Case: Test the double tap behavior
+gTests.push({
+  desc: "Test the double tap behavior",
+
+  run: function() {
+    let browser = gCurrentTab.browser;
+    let width = browser.getBoundingClientRect().width;
+    let height = browser.getBoundingClientRect().height;
+
+    // Should fire "TapSingle"
+    // XXX not working? WTF?
+    info("Test good single tap");
+    clearEvents();
+    EventUtils.synthesizeMouse(browser, width / 2, height / 2, {});
+    todo(checkEvents(["TapSingle"]), "Fired a good single tap");
+    clearEvents();
+
+    setTimeout(function() { gCurrentTest.doubleTapTest(); }, 500);
+  },
+
+  doubleTapTest: function() {
+    let browser = gCurrentTab.browser;
+    let width = browser.getBoundingClientRect().width;
+    let height = browser.getBoundingClientRect().height;
+
+    // Should fire "TapDouble"
+    info("Test good double tap");
+    clearEvents();
+    EventUtils.synthesizeMouse(browser, width / 2, height / 2, {});
+    EventUtils.synthesizeMouse(browser, width / 2, height / 2, {});
+    ok(checkEvents(["TapDouble"]), "Fired a good double tap");
+    clearEvents();
+
+    setTimeout(function() { gCurrentTest.doubleTapFailTest(); }, 500);
+  },
+
+  doubleTapFailTest: function() {
+    let browser = gCurrentTab.browser;
+    let width = browser.getBoundingClientRect().width;
+    let height = browser.getBoundingClientRect().height;
+
+    // Should fire "TapSingle", "TapSingle"
+    info("Test two single taps in different locations");
+    clearEvents();
+    EventUtils.synthesizeMouse(browser, width / 4, height / 4, {});
+    EventUtils.synthesizeMouse(browser, width * 3 / 4, height * 3 / 4, {});
+    ok(checkEvents(["TapSingle", "TapSingle"]), "Fired two single taps in different places, not a double tap");
+    clearEvents();
+
+    setTimeout(function() { gCurrentTest.tapPanTest(); }, 500);
+  },
+
+  tapPanTest: function() {
+    let browser = gCurrentTab.browser;
+    let width = browser.getBoundingClientRect().width;
+    let height = browser.getBoundingClientRect().height;
+
+    info("Test a pan - non-tap event");
+    clearEvents();
+    EventUtils.synthesizeMouse(browser, width / 2, height / 4, { type: "mousedown" });
+    EventUtils.synthesizeMouse(browser, width / 2, height * 3 / 4, { type: "mousemove" });
+    EventUtils.synthesizeMouse(browser, width / 2, height * 3 / 4, { type: "mouseup" });
+    ok(checkEvents([]), "Fired a pan which should be seen as a non event");
+    clearEvents();
+    
+    setTimeout(function() { gCurrentTest.longTapFailTest(); }, 500);
+  },
+
+  longTapFailTest: function() {
+    let browser = gCurrentTab.browser;
+    let width = browser.getBoundingClientRect().width;
+    let height = browser.getBoundingClientRect().height;
+
+    info("Test a long pan - non-tap event");
+    clearEvents();
+    EventUtils.synthesizeMouse(browser, width / 2, height / 4, { type: "mousedown" });
+    EventUtils.synthesizeMouse(browser, width / 2, height * 3 / 4, { type: "mousemove" });
+    setTimeout(function() {
+      EventUtils.synthesizeMouse(browser, width / 2, height * 3 / 4, { type: "mouseup" });
+      ok(checkEvents([]), "Fired a pan + delay which should be seen as a non-event");
+      clearEvents();
+
+      gCurrentTest.longTapPassTest();
+    }, 500);
+  },
+
+  longTapPassTest: function() {
+    let browser = gCurrentTab.browser;
+    let width = browser.getBoundingClientRect().width;
+    let height = browser.getBoundingClientRect().height;
+
+    info("Test a good long pan");
+    clearEvents();
+    EventUtils.synthesizeMouse(browser, width / 2, height / 4, { type: "mousedown" });
+    setTimeout(function() {
+      EventUtils.synthesizeMouse(browser, width / 2, height / 4, { type: "mouseup" });
+      ok(checkEvents(["TapLong"]), "Fired a good long tap");
+      clearEvents();
+
+      gCurrentTest.contextPlainLinkTest();
+    }, 500);
+  },
+
+  contextPlainLinkTest: function() {
+    let browser = gCurrentTab.browser;
+    browser.messageManager.addMessageListener("Browser:ContextMenu", dumpMessages);
+
+    let link = browser.contentDocument.getElementById("link-single");
+    let linkRect = link.getBoundingClientRect();
+
+    clearContextTypes();
+    EventUtils.synthesizeMouseForContent(link, 1, 1, { type: "mousedown" }, window);
+    setTimeout(function() {
+      EventUtils.synthesizeMouseForContent(link, 1, 1, { type: "mouseup" }, window);
+      ok(checkContextTypes(["link","link-saveable"]), "Plain link context types");
+      clearContextTypes();
+
+      gCurrentTest.contextPlainImageTest();
+    }, 500);
+  },
+
+  contextPlainImageTest: function() {
+    let browser = gCurrentTab.browser;
+    browser.messageManager.addMessageListener("Browser:ContextMenu", dumpMessages);
+
+    let img = browser.contentDocument.getElementById("img-single");
+    let imgRect = img.getBoundingClientRect();
+
+    clearContextTypes();
+    EventUtils.synthesizeMouseForContent(img, 1, 1, { type: "mousedown" }, window);
+    setTimeout(function() {
+      EventUtils.synthesizeMouseForContent(img, 1, 1, { type: "mouseup" }, window);
+      ok(checkContextTypes(["image","image-shareable","image-loaded"]), "Plain image context types");
+      clearContextTypes();
+
+      gCurrentTest.contextNestedImageTest();
+    }, 500);
+  },
+
+  contextNestedImageTest: function() {
+    let browser = gCurrentTab.browser;
+    browser.messageManager.addMessageListener("Browser:ContextMenu", dumpMessages);
+
+    let img = browser.contentDocument.getElementById("img-nested");
+    let imgRect = img.getBoundingClientRect();
+
+    clearContextTypes();
+    EventUtils.synthesizeMouseForContent(img, 1, 1, { type: "mousedown" }, window);
+    setTimeout(function() {
+      EventUtils.synthesizeMouseForContent(img, 1, 1, { type: "mouseup" }, window);
+      ok(checkContextTypes(["link","link-saveable","image","image-shareable","image-loaded"]), "Nested image context types");
+      clearContextTypes();
+
+      gCurrentTest.lastTest();
+    }, 500);
+  },
+
+  lastTest: function() {
+    gCurrentTab.browser.messageManager.removeMessageListener("Browser:ContextMenu", dumpMessages);
+
+    runNextTest();
+  }
+});
+
--- a/mobile/chrome/tests/head.js
+++ b/mobile/chrome/tests/head.js
@@ -1,23 +1,41 @@
 /*=============================================================================
   Common Helpers functions
 =============================================================================*/
+
+// Wait for a condition and call a supplied callback if condition is met within
+// alloted time. If condition is not met, cause a hard failure, stopping the test.
 function waitFor(callback, test, timeout) {
   if (test()) {
     callback();
     return;
   }
 
   timeout = timeout || Date.now();
   if (Date.now() - timeout > 1000)
     throw "waitFor timeout";
   setTimeout(waitFor, 50, callback, test, timeout);
 };
 
+// Wait for a condition and call a supplied callback if condition is met within
+// alloted time. If condition is not met, continue anyway. Use this helper if the
+// callback will test for the outcome, but not stop the entire test.
+function waitForAndContinue(callback, test, timeout) {
+  if (test()) {
+    callback();
+    return;
+  }
+
+  timeout = timeout || Date.now();
+  if (Date.now() - timeout > 1000)
+    callback();
+  setTimeout(waitFor, 50, callback, test, timeout);
+};
+
 function makeURI(spec) {
   return Services.io.newURI(spec, null, null);
 };
 
 EventUtils.synthesizeString = function synthesizeString(aString, aWindow) {
   for (let i = 0; i < aString.length; i++) {
     EventUtils.synthesizeKey(aString.charAt(i), {}, aWindow);
   }