Bug 1273654 - Extract a helper function to run continuation-style tests. r?botond draft
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 25 May 2016 10:00:07 -0400
changeset 370881 812322f9f073cdc55da29e52ab61328cd61a8f2b
parent 370880 057f2817785fd967b12a279613f0203d9bf4f3ad
child 370882 7edb10f1f23d25dadde783dd1a8442b50979247a
push id19168
push userkgupta@mozilla.com
push dateWed, 25 May 2016 14:24:41 +0000
reviewersbotond
bugs1273654
milestone49.0a1
Bug 1273654 - Extract a helper function to run continuation-style tests. r?botond MozReview-Commit-ID: 921Js7Frto7
gfx/layers/apz/test/mochitest/apz_test_utils.js
gfx/layers/apz/test/mochitest/helper_bug1271432.html
gfx/layers/apz/test/mochitest/helper_drag_click.html
gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
gfx/layers/apz/test/mochitest/test_frame_reconstruction.html
gfx/layers/apz/test/mochitest/test_layerization.html
gfx/layers/apz/test/mochitest/test_wheel_scroll.html
gfx/layers/apz/test/mochitest/test_wheel_transactions.html
--- a/gfx/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -228,8 +228,42 @@ function isApzEnabled() {
   if (!enabled) {
     // All tests are required to have at least one assertion. Since APZ is
     // disabled, and the main test is presumably not going to run, we stick in
     // a dummy assertion here to keep the test passing.
     SimpleTest.ok(true, "APZ is not enabled; this test will be skipped");
   }
   return enabled;
 }
+
+// Despite what this function name says, this does not *directly* run the
+// provided continuation testFunction. Instead, it returns a function that
+// can be used to run the continuation. The extra level of indirection allows
+// it to be more easily added to a promise chain, like so:
+//   waitUntilApzStable().then(runContinuation(myTest));
+//
+// If you want to run the continuation directly, outside of a promise chain,
+// you can invoke the return value of this function, like so:
+//   runContinuation(myTest)();
+function runContinuation(testFunction) {
+  // We need to wrap this in an extra function, so that the call site can
+  // be more readable without running the promise too early. In other words,
+  // if we didn't have this extra function, the promise would start running
+  // during construction of the promise chain, concurrently with the first
+  // promise in the chain.
+  return function() {
+    return new Promise(function(resolve, reject) {
+      var gTestContinuation = null;
+
+      function driveTest() {
+        if (!gTestContinuation) {
+          gTestContinuation = testFunction(driveTest);
+        }
+        var ret = gTestContinuation.next();
+        if (ret.done) {
+          resolve();
+        }
+      }
+
+      driveTest();
+    });
+  };
+}
--- a/gfx/layers/apz/test/mochitest/helper_bug1271432.html
+++ b/gfx/layers/apz/test/mochitest/helper_bug1271432.html
@@ -1,54 +1,45 @@
 <head>
   <title>Ensure that the hit region doesn't get unexpectedly expanded</title>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
 <script type="application/javascript">
-function* runTest() {
+function* test(testDriver) {
   var scroller = document.getElementById('scroller');
   var scrollerPos = scroller.scrollTop;
   var dx = 100, dy = 50;
 
   is(window.scrollY, 0, "Initial page scroll position should be 0");
   is(scrollerPos, 0, "Initial scroller position should be 0");
 
-  yield synthesizeNativeMouseMoveAndWaitForMoveEvent(scroller, dx, dy, driveTest);
-  yield synthesizeNativeWheelAndWaitForScrollEvent(scroller, dx, dy, 0, -10, driveTest);
+  yield synthesizeNativeMouseMoveAndWaitForMoveEvent(scroller, dx, dy, testDriver);
+  yield synthesizeNativeWheelAndWaitForScrollEvent(scroller, dx, dy, 0, -10, testDriver);
 
   is(window.scrollY, 0, "Page scroll position should still be 0");
   ok(scroller.scrollTop > scrollerPos, "Scroller should have scrolled");
 
   // wait for it to layerize fully and then try again
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
   scrollerPos = scroller.scrollTop;
 
   // The mouse is already at the right position. If we call scrollWheelOver it
   // hangs on windows waiting for the mouse-move, so instead we just synthesize
   // the wheel directly.
-  yield synthesizeNativeWheelAndWaitForScrollEvent(scroller, dx, dy, 0, -10, driveTest);
+  yield synthesizeNativeWheelAndWaitForScrollEvent(scroller, dx, dy, 0, -10, testDriver);
   is(window.scrollY, 0, "Page scroll position should still be 0 after layerization");
   ok(scroller.scrollTop > scrollerPos, "Scroller should have continued scrolling");
 }
 
-var gTestContinuation = null;
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    subtestDone();
-  }
-}
-
-waitUntilApzStable().then(driveTest);
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
 
 </script>
 <style>
 a#with_after_content {
     background-color: #F16725;
     opacity: 0.8;
     display: inline-block;
     margin-top: 40px;
--- a/gfx/layers/apz/test/mochitest/helper_drag_click.html
+++ b/gfx/layers/apz/test/mochitest/helper_drag_click.html
@@ -4,46 +4,38 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width; initial-scale=1.0">
   <title>Sanity mouse-drag click test</title>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <script type="application/javascript">
 
-function* runTest() {
+function* test(testDriver) {
   document.addEventListener('click', clicked, false);
 
   // mouse down, move it around, and release it near where it went down. this
   // should generate a click at the release point
-  yield synthesizeNativeMouseEvent(document.getElementById('b'), 5, 5, nativeMouseDownEventMsg(), driveTest);
-  yield synthesizeNativeMouseEvent(document.getElementById('b'), 100, 100, nativeMouseMoveEventMsg(), driveTest);
-  yield synthesizeNativeMouseEvent(document.getElementById('b'), 10, 10, nativeMouseMoveEventMsg(), driveTest);
-  yield synthesizeNativeMouseEvent(document.getElementById('b'), 8, 8, nativeMouseUpEventMsg(), driveTest);
+  yield synthesizeNativeMouseEvent(document.getElementById('b'), 5, 5, nativeMouseDownEventMsg(), testDriver);
+  yield synthesizeNativeMouseEvent(document.getElementById('b'), 100, 100, nativeMouseMoveEventMsg(), testDriver);
+  yield synthesizeNativeMouseEvent(document.getElementById('b'), 10, 10, nativeMouseMoveEventMsg(), testDriver);
+  yield synthesizeNativeMouseEvent(document.getElementById('b'), 8, 8, nativeMouseUpEventMsg(), testDriver);
   dump("Finished synthesizing click with a drag in the middle\n");
 }
 
 function clicked(e) {
   // The mouse down at (5, 5) should not have generated a click, but the up
   // at (8, 8) should have.
   is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
   is(e.clientX, 8 + Math.floor(document.getElementById('b').getBoundingClientRect().left), 'x-coord of click event looks sane');
   is(e.clientY, 8 + Math.floor(document.getElementById('b').getBoundingClientRect().top), 'y-coord of click event looks sane');
   subtestDone();
 }
 
-var gTestContinuation = null;
-
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  gTestContinuation.next();
-}
-
-waitUntilApzStable().then(driveTest);
+waitUntilApzStable()
+.then(runContinuation(test));
 
   </script>
 </head>
 <body>
  <button id="b" style="width: 10px; height: 10px"></button>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
+++ b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
@@ -2,82 +2,73 @@
   <meta name="viewport" content="width=device-width; initial-scale=1.0">
   <title>Wheel-scrolling over position:fixed and position:sticky elements, in the top-level document as well as iframes</title>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <script type="application/javascript">
 
 // Scroll the mouse wheel at (dx, dy) relative to |element|.
-function scrollWheelOver(element, dx, dy) {
+function scrollWheelOver(element, dx, dy, testDriver) {
   // Move the mouse to the desired wheel location.
   // Not doing so can result in the wheel events from two consecutive
   // scrollWheelOver() calls on different elements being incorrectly considered
   // as part of the same wheel transaction.
   // We also wait for the mouse move event to be processed before sending the
   // wheel event, otherwise there is a chance they might get reordered, and
   // we have the transaction problem again.
   return synthesizeNativeMouseMoveAndWaitForMoveEvent(element, dx, dy, function() {
-    synthesizeNativeWheelAndWaitForScrollEvent(element, dx, dy, 0, -10, driveTest);
+    synthesizeNativeWheelAndWaitForScrollEvent(element, dx, dy, 0, -10, testDriver);
   });
 }
 
-function* runTest() {
+function* test(testDriver) {
   var iframeWin = document.getElementById('iframe').contentWindow;
 
   // scroll over the middle of the iframe's position:sticky element, check
   // that it scrolls the iframe
   var scrollPos = iframeWin.scrollY;
-  yield scrollWheelOver(iframeWin.document.body, 50, 150);
+  yield scrollWheelOver(iframeWin.document.body, 50, 150, testDriver);
   ok(iframeWin.scrollY > scrollPos, "iframe scrolled after wheeling over the position:sticky element");
 
   // same, but using the iframe's position:fixed element
   scrollPos = iframeWin.scrollY;
-  yield scrollWheelOver(iframeWin.document.body, 250, 150);
+  yield scrollWheelOver(iframeWin.document.body, 250, 150, testDriver);
   ok(iframeWin.scrollY > scrollPos, "iframe scrolled after wheeling over the position:fixed element");
 
   // same, but scrolling the scrollable frame *inside* the position:fixed item
   var fpos = document.getElementById('fpos_scrollable');
   scrollPos = fpos.scrollTop;
-  yield scrollWheelOver(fpos, 50, 150);
+  yield scrollWheelOver(fpos, 50, 150, testDriver);
   ok(fpos.scrollTop > scrollPos, "scrollable item inside fixed-pos element scrolled");
   // wait for it to layerize fully and then try again
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
   scrollPos = fpos.scrollTop;
   // The mouse is already at the right position. If we call scrollWheelOver it
   // hangs on windows waiting for the mouse-move, so instead we just synthesize
   // the wheel directly.
-  yield synthesizeNativeWheelAndWaitForScrollEvent(fpos, 50, 150, 0, -10, driveTest);
+  yield synthesizeNativeWheelAndWaitForScrollEvent(fpos, 50, 150, 0, -10, testDriver);
   ok(fpos.scrollTop > scrollPos, "scrollable item inside fixed-pos element scrolled after layerization");
 
   // same, but using the top-level window's position:sticky element
   scrollPos = window.scrollY;
-  yield scrollWheelOver(document.body, 50, 150);
+  yield scrollWheelOver(document.body, 50, 150, testDriver);
   ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:sticky element");
 
   // same, but using the top-level window's position:fixed element
   scrollPos = window.scrollY;
-  yield scrollWheelOver(document.body, 250, 150);
+  yield scrollWheelOver(document.body, 250, 150, testDriver);
   ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:fixed element");
 }
 
-var gTestContinuation = null;
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    subtestDone();
-  }
-}
-
-waitUntilApzStable().then(driveTest);
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
 
   </script>
 </head>
 <body style="height:5000px; margin:0">
   <div style="position:sticky; width: 100px; height: 300px; top: 0; background-color:red">sticky</div>
   <div style="position:fixed; width: 100px; height: 300px; top: 0; left: 200px; background-color: green">fixed</div>
   <iframe id='iframe' width="300" height="400" src="data:text/html,<body style='height:5000px; margin:0'><div style='position:sticky; width:100px; height:300px; top: 0; background-color:red'>sticky</div><div style='position:fixed; right:0; top: 0; width:100px; height:300px; background-color:green'>fixed</div>"></iframe>
 
--- a/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html
+++ b/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html
@@ -149,33 +149,33 @@
     <li>Some text</li>
    </ol>
   </div>
  </div>
 </div>
 
 <pre id="test">
 <script type="application/javascript;version=1.7">
-function* runTest() {
+function* test(testDriver) {
   var elm = document.getElementsByClassName('inner')[0];
   elm.scrollTop = 0;
-  yield flushApzRepaints(driveTest);
+  yield flushApzRepaints(testDriver);
 
   // Take over control of the refresh driver and compositor
   var utils = SpecialPowers.DOMWindowUtils;
   utils.advanceTimeAndRefresh(0);
 
   // Kick off an APZ smooth-scroll to 0,200
   elm.scrollTo(0, 200);
-  yield waitForAllPaints(function() { setTimeout(driveTest, 0); });
+  yield waitForAllPaints(function() { setTimeout(testDriver, 0); });
 
   // Let's do a couple of frames of the animation, and make sure it's going
   utils.advanceTimeAndRefresh(16);
   utils.advanceTimeAndRefresh(16);
-  yield flushApzRepaints(driveTest);
+  yield flushApzRepaints(testDriver);
   ok(elm.scrollTop > 0, "APZ animation in progress", "scrollTop is now " + elm.scrollTop);
   ok(elm.scrollTop < 200, "APZ animation not yet completed", "scrollTop is now " + elm.scrollTop);
 
   var frameReconstructionTriggered = 0;
   // Register the listener that triggers the frame reconstruction
   elm.onscroll = function() {
     // Do the reconstruction
     elm.parentNode.classList.add('contentBefore');
@@ -185,43 +185,34 @@ function* runTest() {
       elm.parentNode.classList.remove('contentBefore');
     }, 0);
   }
 
   // and do a few more frames of the animation, this should trigger the listener
   // and the frame reconstruction
   utils.advanceTimeAndRefresh(16);
   utils.advanceTimeAndRefresh(16);
-  yield flushApzRepaints(driveTest);
+  yield flushApzRepaints(testDriver);
   ok(elm.scrollTop < 200, "APZ animation not yet completed", "scrollTop is now " + elm.scrollTop);
   ok(frameReconstructionTriggered > 0, "Frame reconstruction triggered", "reconstruction triggered " + frameReconstructionTriggered + " times");
 
   // and now run to completion
   for (var i = 0; i < 100; i++) {
     utils.advanceTimeAndRefresh(16);
   }
   utils.restoreNormalRefresh();
-  yield waitForAllPaints(function() { setTimeout(driveTest, 0); });
-  yield flushApzRepaints(driveTest);
+  yield waitForAllPaints(function() { setTimeout(testDriver, 0); });
+  yield flushApzRepaints(testDriver);
 
   is(elm.scrollTop, 200, "Element should have scrolled by 200px");
 }
 
-var gTestContinuation = null;
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    SimpleTest.finish();
-  }
-}
-
 if (isApzEnabled()) {
   SimpleTest.waitForExplicitFinish();
   SimpleTest.expectAssertions(0, 1); // this test triggers an assertion, see bug 1247050
-  waitUntilApzStable().then(driveTest);
+  waitUntilApzStable()
+  .then(runContinuation(test))
+  .then(SimpleTest.finish);
 }
 
 </script>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_layerization.html
+++ b/gfx/layers/apz/test/mochitest/test_layerization.html
@@ -49,36 +49,35 @@ https://bugzilla.mozilla.org/show_bug.cg
      optimization that layerizes the primary async-scrollable frame on page
      load layerizes it rather than its child subframes. -->
   <div id="container-content"></div>
 </div>
 <pre id="test">
 <script type="application/javascript;version=1.7">
 
 // Scroll the mouse wheel over |element|.
-function scrollWheelOver(element, waitForScroll = true) {
+function scrollWheelOver(element, waitForScroll, testDriver) {
   var x = 10;
   var y = 10;
   // Move the mouse to the desired wheel location.
   // Not doing so can result in the wheel events from two consecutive
   // scrollWheelOver() calls on different elements being incorrectly considered
   // as part of the same wheel transaction.
   // We also wait for the mouse move event to be processed before sending the
   // wheel event, otherwise there is a chance they might get reordered, and
   // we have the transaction problem again.
   synthesizeNativeMouseMoveAndWaitForMoveEvent(element, x, y, function() {
     if (waitForScroll) {
-      synthesizeNativeWheelAndWaitForScrollEvent(element, x, y, 0, -10, driveTest);
+      synthesizeNativeWheelAndWaitForScrollEvent(element, x, y, 0, -10, testDriver);
     } else {
-      synthesizeNativeWheelAndWaitForWheelEvent(element, x, y, 0, -10, driveTest);
+      synthesizeNativeWheelAndWaitForWheelEvent(element, x, y, 0, -10, testDriver);
     }
   });
 }
 
-var gTestContinuation = null;
 var utils;
 
 const DISPLAYPORT_EXPIRY = 100;
 
 // Return whether the element with id |elementId| has been layerized.
 // Assumes |elementId| will be present in the content description for the
 // element, and not in the content descriptions of other elements.
 function isLayerized(elementId) {
@@ -92,152 +91,147 @@ function isLayerized(elementId) {
       if (paint[scrollId]["contentDescription"].includes(elementId)) {
         return true;
       }
     }
   }
   return false;
 }
 
-// Helper function to pass to waitForAllPaints rather than passing driveTest
-// directly. If there are no paints pending waitForAllPaints will invoke the
-// callback synchronously, and if we did waitForAllPaints(driveTest) that might
-// cause reentrancy into driveTest which is bad.
-function callDriveTestAsync() {
-  setTimeout(driveTest, 0);
+// This helper function produces another helper function, which, when invoked,
+// invokes the provided testDriver argument in a setTimeout 0. This is really
+// just useful in cases when there are no paints pending, because then
+// waitForAllPaints will invoke its callback synchronously. If we did
+// waitForAllPaints(testDriver) that might cause reentrancy into the testDriver
+// which is bad. This function works around that.
+function asyncWrapper(testDriver) {
+  return function() {
+    setTimeout(testDriver, 0);
+  };
 }
 
-function* runTest() {
+function* test(testDriver) {
   utils = SpecialPowers.getDOMWindowUtils(window);
 
   // Initially, nothing should be layerized.
   ok(!isLayerized('outer1'), "initially 'outer1' should not be layerized");
   ok(!isLayerized('inner1'), "initially 'inner1' should not be layerized");
   ok(!isLayerized('outer2'), "initially 'outer2' should not be layerized");
   ok(!isLayerized('inner2'), "initially 'inner2' should not be layerized");
   ok(!isLayerized('outer3'), "initially 'outer3' should not be layerized");
   ok(!isLayerized('inner3'), "initially 'inner3' should not be layerized");
   ok(!isLayerized('outer4'), "initially 'outer4' should not be layerized");
   ok(!isLayerized('inner4'), "initially 'inner4' should not be layerized");
 
   // Scrolling over outer1 should layerize outer1, but not inner1.
-  yield scrollWheelOver(document.getElementById('outer1'));
+  yield scrollWheelOver(document.getElementById('outer1'), true, testDriver);
   ok(isLayerized('outer1'), "scrolling 'outer1' should cause it to be layerized");
   ok(!isLayerized('inner1'), "scrolling 'outer1' should not cause 'inner1' to be layerized");
 
   // Scrolling over inner2 should layerize both outer2 and inner2.
-  yield scrollWheelOver(document.getElementById('inner2'));
+  yield scrollWheelOver(document.getElementById('inner2'), true, testDriver);
   ok(isLayerized('inner2'), "scrolling 'inner2' should cause it to be layerized");
   ok(isLayerized('outer2'), "scrolling 'inner2' should also cause 'outer2' to be layerized");
 
   // The second half of the test repeats the same checks as the first half,
   // but with an iframe as the outer scrollable frame.
 
   // Scrolling over outer3 should layerize outer3, but not inner3.
-  yield scrollWheelOver(document.getElementById('outer3').contentDocument.documentElement);
+  yield scrollWheelOver(document.getElementById('outer3').contentDocument.documentElement, true, testDriver);
   ok(isLayerized('outer3'), "scrolling 'outer3' should cause it to be layerized");
   ok(!isLayerized('inner3'), "scrolling 'outer3' should not cause 'inner3' to be layerized");
 
   // Scrolling over outer4 should layerize both outer4 and inner4.
-  yield scrollWheelOver(document.getElementById('outer4').contentDocument.getElementById('inner4'));
+  yield scrollWheelOver(document.getElementById('outer4').contentDocument.getElementById('inner4'), true, testDriver);
   ok(isLayerized('inner4'), "scrolling 'inner4' should cause it to be layerized");
   ok(isLayerized('outer4'), "scrolling 'inner4' should also cause 'outer4' to be layerized");
 
   // Now we enable displayport expiry, and verify that things are still
   // layerized as they were before.
-  yield SpecialPowers.pushPrefEnv({"set": [["apz.displayport_expiry_ms", DISPLAYPORT_EXPIRY]]}, driveTest);
+  yield SpecialPowers.pushPrefEnv({"set": [["apz.displayport_expiry_ms", DISPLAYPORT_EXPIRY]]}, testDriver);
   ok(isLayerized('outer1'), "outer1 is still layerized after enabling expiry");
   ok(!isLayerized('inner1'), "inner1 is still not layerized after enabling expiry");
   ok(isLayerized('outer2'), "outer2 is still layerized after enabling expiry");
   ok(isLayerized('inner2'), "inner2 is still layerized after enabling expiry");
   ok(isLayerized('outer3'), "outer3 is still layerized after enabling expiry");
   ok(!isLayerized('inner3'), "inner3 is still not layerized after enabling expiry");
   ok(isLayerized('outer4'), "outer4 is still layerized after enabling expiry");
   ok(isLayerized('inner4'), "inner4 is still layerized after enabling expiry");
 
   // Now we trigger a scroll on some of the things still layerized, so that
   // the displayport expiry gets triggered.
 
   // Expire displayport with scrolling on outer1
-  yield scrollWheelOver(document.getElementById('outer1'));
+  yield scrollWheelOver(document.getElementById('outer1'), true, testDriver);
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(!isLayerized('outer1'), "outer1 is no longer layerized after displayport expiry");
   ok(!isLayerized('inner1'), "inner1 is still not layerized after displayport expiry");
 
   // Expire displayport with scrolling on inner2
-  yield scrollWheelOver(document.getElementById('inner2'));
+  yield scrollWheelOver(document.getElementById('inner2'), true, testDriver);
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
   // Once the expiry elapses, it will trigger expiry on outer2, so we check
   // both, one at a time.
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(!isLayerized('inner2'), "inner2 is no longer layerized after displayport expiry");
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(!isLayerized('outer2'), "outer2 got de-layerized with inner2");
 
   // Scroll on inner3. inner3 isn't layerized, and this will cause it to
   // get layerized, but it will also trigger displayport expiration for inner3
   // which will eventually trigger displayport expiration on inner3 and outer3.
   // Note that the displayport expiration might actually happen before the wheel
   // input is processed in the compositor (see bug 1246480 comment 3), and so
   // we make sure not to wait for a scroll event here, since it may never fire.
-  yield scrollWheelOver(document.getElementById('outer3').contentDocument.getElementById('inner3'), false);
+  yield scrollWheelOver(document.getElementById('outer3').contentDocument.getElementById('inner3'), false, testDriver);
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(!isLayerized('inner3'), "inner3 becomes unlayerized after expiry");
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(!isLayerized('outer3'), "outer3 is no longer layerized after inner3 triggered expiry");
 
   // Scroll outer4 and wait for the expiry. It should NOT get expired because
   // inner4 is still layerized
-  yield scrollWheelOver(document.getElementById('outer4').contentDocument.documentElement);
+  yield scrollWheelOver(document.getElementById('outer4').contentDocument.documentElement, true, testDriver);
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
   // Wait for the expiry to elapse
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(isLayerized('inner4'), "inner4 is still layerized because it never expired");
   ok(isLayerized('outer4'), "outer4 is still layerized because inner4 is still layerized");
 }
 
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    SimpleTest.finish();
-  }
-}
-
 if (isApzEnabled()) {
   SimpleTest.waitForExplicitFinish();
   SimpleTest.requestFlakyTimeout("we are testing code that measures an actual timeout");
   SimpleTest.expectAssertions(0, 8); // we get a bunch of "ASSERTION: Bounds computation mismatch" sometimes (bug 1232856)
 
   // Disable smooth scrolling, because it results in long-running scroll
   // animations that can result in a 'scroll' event triggered by an earlier
   // wheel event as corresponding to a later wheel event.
   // Also enable APZ test logging, since we use that data to determine whether
   // a scroll frame was layerized.
   pushPrefs([["general.smoothScroll", false],
              ["apz.displayport_expiry_ms", 0],
              ["apz.test.logging_enabled", true]])
   .then(waitUntilApzStable)
-  .then(driveTest);
+  .then(runContinuation(test))
+  .then(SimpleTest.finish);
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_wheel_scroll.html
+++ b/gfx/layers/apz/test/mochitest/test_wheel_scroll.html
@@ -72,47 +72,35 @@ var incrementForMode = function (mode) {
 
 document.getElementById("scrollbox").addEventListener("wheel", function (e) {
   rotation += e.deltaY * incrementForMode(e.deltaMode) * 0.2;
   document.getElementById("circle").style.transform = "rotate(" + rotation + "deg)";
   rotationAdjusted = true;
   e.preventDefault();
 });
 
-function* runTest() {
+function* test(testDriver) {
   var content = document.getElementById('content');
   for (i = 0; i < 300; i++) { // enough iterations that we would scroll to the bottom of 'content'
-    yield synthesizeNativeWheelAndWaitForWheelEvent(content, 100, 150, 0, -5, continueTest);
+    yield synthesizeNativeWheelAndWaitForWheelEvent(content, 100, 150, 0, -5, testDriver);
   }
   var scrollbox = document.getElementById('scrollbox');
   is(content.scrollTop > 0, true, "We should have scrolled down somewhat");
   is(content.scrollTop < content.scrollTopMax, true, "We should not have scrolled to the bottom of the scrollframe");
   is(rotationAdjusted, true, "The rotation should have been adjusted");
 }
 
-var gTestContinuation = null;
-function continueTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    SimpleTest.finish();
-  } else {
-    is(ret.value, true, "Wheel event successfully synthesized");
-  }
-}
-
 SimpleTest.testInChaosMode();
 SimpleTest.waitForExplicitFinish();
 
 // If we allow smooth scrolling the "smooth" scrolling may cause the page to
 // glide past the scrollbox (which is supposed to stop the scrolling) and so
 // we might end up at the bottom of the page.
 pushPrefs([["general.smoothScroll", false]])
 .then(waitUntilApzStable)
-.then(continueTest);
+.then(runContinuation(test))
+.then(SimpleTest.finish);
 
 </script>
 </pre>
 
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_wheel_transactions.html
+++ b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html
@@ -36,112 +36,102 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="outer-frame">
     <div id="inner-frame">
         <div id="inner-content"></div>
     </div>
 </div>
 <pre id="test">
 <script type="application/javascript;version=1.7">
 
-function scrollWheelOver(element, deltaY) {
-  synthesizeNativeWheelAndWaitForScrollEvent(element, 10, 10, 0, deltaY, driveTest);
+function scrollWheelOver(element, deltaY, testDriver) {
+  synthesizeNativeWheelAndWaitForScrollEvent(element, 10, 10, 0, deltaY, testDriver);
 }
 
-function* runTest() {
+function* test(testDriver) {
   var outer = document.getElementById('outer-frame');
   var inner = document.getElementById('inner-frame');
   var innerContent = document.getElementById('inner-content');
 
   // Register a wheel event listener that records the target of
   // the last wheel event, so that we can make assertions about it.
   var lastWheelTarget;
   var wheelTargetRecorder = function(e) { lastWheelTarget = e.target; };
   window.addEventListener("wheel", wheelTargetRecorder);
 
   // Scroll |outer| to the bottom.
   while (outer.scrollTop < outer.scrollTopMax) {
-    yield scrollWheelOver(outer, -10);
+    yield scrollWheelOver(outer, -10, testDriver);
   }
 
   // Verify that this has brought |inner| under the wheel.
   is(lastWheelTarget, innerContent, "'inner-content' should have been brought under the wheel");
   window.removeEventListener("wheel", wheelTargetRecorder);
 
   // Immediately after, scroll it back up a bit.
-  yield scrollWheelOver(outer, 10);
+  yield scrollWheelOver(outer, 10, testDriver);
 
   // Check that it was |outer| that scrolled back, and |inner| didn't
   // scroll at all, as all the above scrolls should be in the same
   // transaction.
   ok(outer.scrollTop < outer.scrollTopMax, "'outer' should have scrolled back a bit");
   is(inner.scrollTop, 0, "'inner' should not have scrolled");
 
   // The next part of the test is related to the transaction timeout.
   // Turn it down a bit so waiting for the timeout to elapse doesn't
   // slow down the test harness too much.
   var timeout = 5;
-  yield SpecialPowers.pushPrefEnv({"set": [["mousewheel.transaction.timeout", timeout]]}, driveTest);
+  yield SpecialPowers.pushPrefEnv({"set": [["mousewheel.transaction.timeout", timeout]]}, testDriver);
   SimpleTest.requestFlakyTimeout("we are testing code that measures actual elapsed time between two events");
 
   // Scroll up a bit more. It's still |outer| scrolling because
   // |inner| is still scrolled all the way to the top.
-  yield scrollWheelOver(outer, 10);
+  yield scrollWheelOver(outer, 10, testDriver);
 
   // Wait for the transaction timeout to elapse.
   // timeout * 5 is used to make it less likely that the timeout is less than
   // the system timestamp resolution
-  yield window.setTimeout(driveTest, timeout * 5);
+  yield window.setTimeout(testDriver, timeout * 5);
 
   // Now scroll down. The transaction having timed out, the event
   // should pick up a new target, and that should be |inner|.
-  yield scrollWheelOver(outer, -10);
+  yield scrollWheelOver(outer, -10, testDriver);
   ok(inner.scrollTop > 0, "'inner' should have been scrolled");
 
   // Finally, test scroll handoff after a timeout.
 
   // Continue scrolling |inner| down to the bottom.
   var prevScrollTop = inner.scrollTop;
   while (inner.scrollTop < inner.scrollTopMax) {
-    yield scrollWheelOver(outer, -10);
+    yield scrollWheelOver(outer, -10, testDriver);
     // Avoid a failure getting us into an infinite loop.
     ok(inner.scrollTop > prevScrollTop, "scrolling down should increase scrollTop");
     prevScrollTop = inner.scrollTop;
   }
 
   // Wait for the transaction timeout to elapse.
   // timeout * 5 is used to make it less likely that the timeout is less than
   // the system timestamp resolution
-  yield window.setTimeout(driveTest, timeout * 5);
+  yield window.setTimeout(testDriver, timeout * 5);
 
   // Continued downward scrolling should scroll |outer| to the bottom.
   prevScrollTop = outer.scrollTop;
   while (outer.scrollTop < outer.scrollTopMax) {
-    yield scrollWheelOver(outer, -10);
+    yield scrollWheelOver(outer, -10, testDriver);
     // Avoid a failure getting us into an infinite loop.
     ok(outer.scrollTop > prevScrollTop, "scrolling down should increase scrollTop");
     prevScrollTop = outer.scrollTop;
   }
 }
 
-var gTestContinuation = null;
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    SimpleTest.finish();
-  }
-}
-
 SimpleTest.waitForExplicitFinish();
 
 // Disable smooth scrolling because it makes the test flaky (we don't have a good
 // way of detecting when the scrolling is finished).
 pushPrefs([["general.smoothScroll", false]])
 .then(waitUntilApzStable)
-.then(driveTest);
+.then(runContinuation(test))
+.then(SimpleTest.finish);
 
 </script>
 </pre>
 
 </body>
 </html>