Bug 1273654 - Extract a helper function to run continuation-style tests. r=botond
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 25 May 2016 15:31:52 -0400
changeset 338021 1ac523b371f2408d9dc3c403390387564d811962
parent 338020 f163ba08afc61c44fc1b2cacab7df37d8097b1a8
child 338022 e096709134afdeae68afb26f999dc1c0a2f4de39
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1273654
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1273654 - Extract a helper function to run continuation-style tests. r=botond MozReview-Commit-ID: 67WYQr1d7YO
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 testContinuation = null;
+
+      function driveTest() {
+        if (!testContinuation) {
+          testContinuation = testFunction(driveTest);
+        }
+        var ret = testContinuation.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>