Source code
Revision control
Copy as Markdown
Other Tools
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="/tests/SimpleTest/NativeKeyCodes.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<script src="apz_test_utils.js"></script>
<script src="apz_test_native_event_utils.js"></script>
<title>What happens if main thread scrolls?</title>
<style>
html, body { margin: 0; }
html {
  background:
    repeating-linear-gradient(45deg, transparent 0, transparent 100px, rgba(0,0,0,0.1) 0, rgba(0,0,0,0.1) 200px),
    repeating-linear-gradient(-45deg, transparent 0, transparent 100px, rgba(0,0,0,0.1) 0, rgba(0,0,0,0.1) 200px),
    repeating-linear-gradient(to bottom, transparent 0, transparent 500px, rgba(0,0,0,0.4) 0, rgba(0,0,0,0.4) 1000px),
    repeating-linear-gradient(to bottom, hsl(0, 60%, 80%), hsl(0, 60%, 80%) 200px, hsl(70, 60%, 80%) 0, hsl(70, 60%, 80%) 400px, hsl(140, 60%, 80%) 0, hsl(140, 60%, 80%) 600px, hsl(210, 60%, 80%) 0, hsl(210, 60%, 80%) 800px),
    white;
  background-size:
    283px 283px,
    283px 283px,
    100px 1000px,
    100px 800px;
}
body {
  height: 10000px;
}
</style>
<script>
const searchParams = new URLSearchParams(location.search);
let strict = searchParams.get("strict") == "true";
var intervalId;
// Start periodic content expansions after we get a scroll event triggered by
// a key press in test() function below, otherwise we may have same scroll
// offsets caused by this script before we start scrolling.
window.addEventListener("scroll",  () => {
  var offset = 0;
  var initialBodyHeight = 10000;
  intervalId = setInterval(() => {
    // "Add content" at the top. We do this by making the body longer and adjusting the background position.
    offset += 10;
    document.documentElement.style.backgroundPosition = `0px ${offset}px`;
    document.body.style.height = `${initialBodyHeight + offset}px`;
    switch (searchParams.get("scroll-method")) {
      case "scrollBy":
        window.scrollBy(0, 10);
        break;
      case "scrollTop":
        document.scrollingElement.scrollTop += 10;
        break;
      case "scrollTo":
        window.scrollTo(0, window.scrollY + 10);
        break;
      default:
        ok(false, "Unsupported scroll method: " + searchParams.get("scroll-method"));
        break;
    }
    // Simulate some jank.
    var freezeDurationInMilliseconds = 100;
    var startTime = Date.now();
    while (Date.now() - startTime < freezeDurationInMilliseconds) {} // eslint-disable-line no-empty
  }, 300);
}, { once: true });
async function test() {
  // Once this content starts scrolling, it triggers a 100ms jank every 300ms so
  // sending arrow down keys for 1500ms will cause some jank.
  const TEST_DURATION_MS = 1500;
  const timeAtStart = performance.now();
  while (performance.now() - timeAtStart < TEST_DURATION_MS) {
    switch (searchParams.get("input-type")) {
      case "key":
        synthesizeKey("KEY_ArrowDown");
        break;
      case "native-key": {
        const DownArrowKeyCode = nativeArrowDownKey();
        ok(synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US,
                               DownArrowKeyCode, {}  /* no modifier */,
                               "", ""),
          "Dispatched an down arrow key event");
        break;
      }
      case "wheel":
        await synthesizeNativeWheel(window, 50, 50, 0, -50);
        break;
      default:
        ok(false, "Unsupported input type: " + searchParams.get("input-type"));
        break;
    }
    await promiseFrame(window);
  }
  // Stop the periodic expansions.
  clearInterval(intervalId);
  const records = collectSampledScrollOffsets(document.scrollingElement);
  let previousRecord = { scrollOffsetY: 0, sampledTimeStamp: 0 };
  let scrollStartTime = null;
  for (const record of records) {
    // Ignore offsets before scrolling.
    if (record.scrollOffsetY == 0) {
      continue;
    }
    // Ignore offsets after TEST_DURATION_MS has elapsed since the
    // start of scrolling. Note that the sampled timestamps are
    // in microseconds.
    if (!scrollStartTime) {
      scrollStartTime = record.sampledTimeStamp;
    } else if (((record.sampledTimeStamp - scrollStartTime) / 1000) > TEST_DURATION_MS) {
      break;
    }
    ok(
      strict
        ? (record.scrollOffsetY > previousRecord.scrollOffsetY)
        : (record.scrollOffsetY >= previousRecord.scrollOffsetY),
      "scroll offset should be " +
      (strict ? "strictly monotonically increasing " : "nondecreasing ") +
      "previous offset: " + previousRecord.scrollOffsetY +
      ", offset: " + record.scrollOffsetY
    );
    ok(
      record.sampledTimeStamp > previousRecord.sampledTimeStamp,
      "sampled time stamp should be strictly monotonically increasing " +
      "previous timestamp: " + previousRecord.sampledTimeStamp +
      ", timestamp: " + record.sampledTimeStamp
    );
    previousRecord = record;
  }
}
function isOnChaosMode() {
  return SpecialPowers.Services.env.get("MOZ_CHAOSMODE");
}
function startTest() {
  if (searchParams.get("input-type") == "native-key") {
    if (getPlatform() != "mac" && getPlatform() != "windows") {
      ok(true, "Skipping test because native key events are not supported on " +
        getPlatform());
      subtestDone();
      return;
    } else if (getPlatform() == "mac" && isOnChaosMode()) {
      ok(true, "Skipping native-key tests on verify runs on Mac");
      subtestDone();
      return;
    } else if (getPlatform() == "windows") {
      ok(true, "Skipping native-key tests on Windows due to intermittent failures");
      subtestDone();
      return;
    }
  }
  waitUntilApzStable()
  .then(test)
  .then(subtestDone, subtestFailed);
}
startTest();
</script>