Bug 1430173 Add Timer Rounding tests backported from -central to -esr r?baku draft
authorTom Ritter <tom@mozilla.com>
Tue, 20 Feb 2018 13:30:16 -0600
changeset 760146 45dc916c551ca46c2cc82350437aebf281251f90
parent 760145 ea03210ae93f8ecd7cd73966968596005d7f9558
child 760147 c2d3a749740c679268bb3a95a20d2e86b9439b2b
push id100551
push userbmo:tom@mozilla.com
push dateMon, 26 Feb 2018 22:12:44 +0000
reviewersbaku
bugs1430173
milestone52.6.1
Bug 1430173 Add Timer Rounding tests backported from -central to -esr r?baku MozReview-Commit-ID: Jl4WZAamgrI
dom/tests/browser/browser.ini
dom/tests/browser/browser_performanceAPI.js
dom/tests/browser/file_workerPerformance.js
dom/tests/mochitest/general/mochitest.ini
dom/tests/mochitest/general/test_reduce_time_precision.html
dom/tests/mochitest/general/worker_child.js
dom/tests/mochitest/general/worker_grandchild.js
old mode 100644
new mode 100755
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -3,16 +3,17 @@ support-files =
   browser_frame_elements.html
   page_privatestorageevent.html
   position.html
   test-console-api.html
   test_bug1004814.html
   worker_bug1004814.js
   geo_leak_test.html
   dummy.html
+  file_workerPerformance.js
   test_largeAllocation.html
   test_largeAllocation.html^headers^
   !/dom/tests/mochitest/geolocation/network_geolocation.sjs
 
 [browser_allocateGigabyte.js]
 disabled = Does not reliably pass on 32-bit systems - bug 1314098
 skip-if = !e10s
 [browser_autofocus_background.js]
@@ -39,8 +40,9 @@ skip-if = !e10s # Large-Allocation requi
 tags = openwindow
 skip-if = toolkit == 'android'  || (os == "linux" && debug) # see bug 1261495 for Linux debug time outs
 support-files =
   test_new_window_from_content_child.html
 [browser_test_toolbars_visibility.js]
 support-files =
   test_new_window_from_content_child.html
 [browser_xhr_sandbox.js]
+[browser_performanceAPI.js]
new file mode 100755
--- /dev/null
+++ b/dom/tests/browser/browser_performanceAPI.js
@@ -0,0 +1,139 @@
+/**
+ * Bug 1369303 - A test for making sure that performance APIs have been correctly
+ *   spoofed or disabled.
+ */
+
+const TEST_PATH = "http://example.net/browser/" +
+                  "dom/tests/browser/";
+
+const PERFORMANCE_TIMINGS = [
+  "navigationStart",
+  "unloadEventStart",
+  "unloadEventEnd",
+  "redirectStart",
+  "redirectEnd",
+  "fetchStart",
+  "domainLookupStart",
+  "domainLookupEnd",
+  "connectStart",
+  "connectEnd",
+  "requestStart",
+  "responseStart",
+  "responseEnd",
+  "domLoading",
+  "domInteractive",
+  "domContentLoadedEventStart",
+  "domContentLoadedEventEnd",
+  "domComplete",
+  "loadEventStart",
+  "loadEventEnd",
+];
+
+let isRounded = (x, expectedPrecision) => {
+  let rounded = (Math.floor(x / expectedPrecision) * expectedPrecision);
+  // First we do the perfectly normal check that should work just fine
+  if (rounded === x || x === 0)
+    return true;
+
+  // When we're diving by non-whole numbers, we may not get perfect
+  // multiplication/division because of floating points
+  if (Math.abs(rounded - x + expectedPrecision) < .0000001) {
+    return true;
+  } else if (Math.abs(rounded - x) < .0000001) {
+    return true;
+  }
+
+  // Then we handle the case where you're sub-millisecond and the timer is not
+  // We check that the timer is not sub-millisecond by assuming it is not if it
+  // returns an even number of milliseconds
+  if (expectedPrecision < 1 && Math.round(x) == x) {
+    if (Math.round(rounded) == x) {
+      return true;
+    }
+  }
+
+  ok(false, "Looming Test Failure, Additional Debugging Info: Expected Precision: " + expectedPrecision + " Measured Value: " + x +
+    " Rounded Vaue: " + rounded + " Fuzzy1: " + Math.abs(rounded - x + expectedPrecision) +
+    " Fuzzy 2: " + Math.abs(rounded - x));
+
+  return false;
+};
+
+// ================================================================================================
+// ================================================================================================
+add_task(function* () {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(
+    gBrowser, TEST_PATH + "dummy.html");
+
+  yield ContentTask.spawn(tab.linkedBrowser, {
+      list: PERFORMANCE_TIMINGS,
+      precision: 2,
+      isRoundedFunc: isRounded.toString()
+    }, (data) => {
+    let timerlist = data.list;
+    let expectedPrecision = data.precision;
+    // eslint beleives that isrounded is available in this scope, but if you
+    // remove the assignment, you will see it is not
+    // eslint-disable-next-line
+    let isRounded = eval(data.isRoundedFunc);
+
+    // Check that whether the performance timing API is correctly spoofed.
+    for (let time of timerlist) {
+      ok(isRounded(content.performance.timing[time], expectedPrecision), `For reduceTimerPrecision(` + expectedPrecision + `), the timing(${time}) is not correctly rounded: ` + content.performance.timing[time]);
+    }
+
+    // Try to add some entries.
+    content.performance.mark("Test");
+    content.performance.mark("Test-End");
+    content.performance.measure("Test-Measure", "Test", "Test-End");
+
+    // Check the entries for performance.getEntries/getEntriesByType/getEntriesByName.
+    is(content.performance.getEntries().length, 3, "For reduceTimerPrecision, there should be 3 entries for performance.getEntries()");
+    for (var i = 0; i < 3; i++) {
+      let startTime = content.performance.getEntries()[i].startTime;
+      let duration = content.performance.getEntries()[i].duration;
+      ok(isRounded(startTime, expectedPrecision), "For reduceTimerPrecision(" + expectedPrecision + "), performance.getEntries(" + i + ").startTime is not rounded: " + startTime);
+      ok(isRounded(duration, expectedPrecision), "For reduceTimerPrecision(" + expectedPrecision + "), performance.getEntries(" + i + ").duration is not rounded: " + duration);
+    }
+    is(content.performance.getEntriesByType("mark").length, 2, "For reduceTimerPrecision, there should be 2 entries for performance.getEntriesByType()");
+    is(content.performance.getEntriesByName("Test", "mark").length, 1, "For reduceTimerPrecision, there should be 1 entry for performance.getEntriesByName()");
+    content.performance.clearMarks();
+    content.performance.clearMeasures();
+    content.performance.clearResourceTimings();
+  });
+  gBrowser.removeTab(tab);
+});
+
+// ================================================================================================
+// ================================================================================================
+add_task(function*() {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(
+    gBrowser, TEST_PATH + "dummy.html");
+
+  yield ContentTask.spawn(tab.linkedBrowser, {
+    list: PERFORMANCE_TIMINGS,
+    precision: 2,
+    isRoundedFunc: isRounded.toString()
+  }, (data) => {
+    let expectedPrecision = data.precision;
+    let workerCall = data.workerCall;
+    return new Promise(resolve => {
+      let worker = new content.Worker("file_workerPerformance.js");
+      worker.onmessage = function(e) {
+        if (e.data.type == "status") {
+          ok(e.data.status, e.data.msg);
+        } else if (e.data.type == "finish") {
+          worker.terminate();
+          resolve();
+        } else {
+          ok(false, "Unknown message type");
+          worker.terminate();
+          resolve();
+        }
+      };
+    worker.postMessage({precision: expectedPrecision});
+    });
+  });
+
+  gBrowser.removeTab(tab);
+});
new file mode 100755
--- /dev/null
+++ b/dom/tests/browser/file_workerPerformance.js
@@ -0,0 +1,65 @@
+function ok(a, msg) {
+  postMessage({type: "status", status: !!a, msg});
+}
+
+function is(a, b, msg) {
+  ok(a === b, msg);
+}
+
+function finish() {
+  postMessage({type: "finish"});
+}
+
+let isRounded = (x, expectedPrecision) => {
+  let rounded = (Math.floor(x / expectedPrecision) * expectedPrecision);
+  // First we do the perfectly normal check that should work just fine
+  if (rounded === x || x === 0)
+    return true;
+
+  // When we're diving by non-whole numbers, we may not get perfect
+  // multiplication/division because of floating points
+  if (Math.abs(rounded - x + expectedPrecision) < .0000001) {
+    return true;
+  } else if (Math.abs(rounded - x) < .0000001) {
+    return true;
+  }
+
+  // Then we handle the case where you're sub-millisecond and the timer is not
+  // We check that the timer is not sub-millisecond by assuming it is not if it
+  // returns an even number of milliseconds
+  if (expectedPrecision < 1 && Math.round(x) == x) {
+    if (Math.round(rounded) == x) {
+      return true;
+    }
+  }
+
+  ok(false, "Looming Test Failure, Additional Debugging Info: Expected Precision: " + expectedPrecision + " Measured Value: " + x +
+    " Rounded Vaue: " + rounded + " Fuzzy1: " + Math.abs(rounded - x + expectedPrecision) +
+    " Fuzzy 2: " + Math.abs(rounded - x));
+
+  return false;
+};
+
+function runRTPTests(expectedPrecision) {
+  // Try to add some entries.
+  performance.mark("Test");
+  performance.mark("Test-End");
+  performance.measure("Test-Measure", "Test", "Test-End");
+
+  // Check the entries in performance.getEntries/getEntriesByType/getEntriesByName.
+  is(performance.getEntries().length, 3, "In a worker, for reduceTimerPrecision: Incorrect number of entries for performance.getEntries() for workers: " + performance.getEntries().length);
+  for (var i = 0; i < 3; i++) {
+    let startTime = performance.getEntries()[i].startTime;
+    let duration = performance.getEntries()[i].duration;
+    ok(isRounded(startTime, expectedPrecision), "In a worker, for reduceTimerPrecision(" + expectedPrecision + "), performance.getEntries(" + i.toString() + ").startTime is not rounded: " + startTime.toString());
+    ok(isRounded(duration, expectedPrecision), "In a worker, for reduceTimerPrecision(" + expectedPrecision + "), performance.getEntries(" + i.toString() + ").duration is not rounded: " + duration.toString());
+  }
+  is(performance.getEntriesByType("mark").length, 2, "In a worker, for reduceTimerPrecision: Incorrect number of entries for performance.getEntriesByType() for workers: " + performance.getEntriesByType("resource").length);
+  is(performance.getEntriesByName("Test", "mark").length, 1, "In a worker, for reduceTimerPrecision: Incorrect number of entries for performance.getEntriesByName() for workers: " + performance.getEntriesByName("Test", "mark").length);
+
+  finish();
+}
+
+self.onmessage = function(e) {
+  runRTPTests(e.data.precision);
+};
old mode 100644
new mode 100755
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -38,16 +38,18 @@ support-files =
   res8.resource^headers^
   resource_timing.js
   navigation_timing.html
   test_bug1012662_common.js
   frameStorageAllowed.html
   frameStoragePrevented.html
   frameStorageChrome.html
   frameStorageNullprincipal.sjs
+  worker_child.js
+  worker_grandchild.js
   workerStorageAllowed.js
   workerStoragePrevented.js
   storagePermissionsUtils.js
   frameSelectEvents.html
   !/image/test/mochitest/big.png
   !/image/test/mochitest/blue.png
   !/image/test/mochitest/clear.png
   !/image/test/mochitest/damon.jpg
@@ -103,16 +105,17 @@ support-files = test_offsets.js
 [test_outerHTML.xhtml]
 [test_paste_selection.html]
 [test_performance_now.html]
 [test_performance_timeline.html]
 [test_picture_apng.html]
 [test_picture_mutations.html]
 [test_pointerPreserves3D.html]
 [test_pointerPreserves3DClip.html]
+[test_reduce_time_precision.html]
 [test_resource_timing.html]
 [test_resource_timing_cross_origin.html]
 [test_resource_timing_frameset.html]
 [test_selectevents.html]
 skip-if = toolkit == 'android' # bug 1230232 - Mouse doesn't select in the same way
 [test_showModalDialog.html]
 skip-if = e10s || toolkit == 'android' #Don't run modal tests on Android
 [test_showModalDialog_e10s.html]
new file mode 100755
--- /dev/null
+++ b/dom/tests/mochitest/general/test_reduce_time_precision.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tor bug
+https://trac.torproject.org/projects/tor/ticket/1517
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Tor Bug 1517 and Mozilla Bug 1424341</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://trac.torproject.org/projects/tor/ticket/1517">Tor Bug 1517</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1424341">Mozilla Bug 1424341</a>
+
+<!-- Canvas for testing 'currentTime' -->
+<canvas id="test-canvas" width="100" height="100"></canvas>
+
+<!-- The main testing script -->
+<script type="application/javascript">
+  SimpleTest.requestFlakyTimeout("testing JS time-based fingerprinting");
+
+  // Prepare for test of AudioContext.currentTime
+  let audioContext = new AudioContext();
+  // Prepare for test of CanvasStream.currentTime
+  let canvas = document.getElementById("test-canvas");
+  let context = canvas.getContext("2d");
+  context.fillText("test", 20, 20);
+  let canvasStream = canvas.captureStream(25);
+
+  // Known ways to generate time stamps, in milliseconds
+  const timeStampCodes = [
+    "performance.now()",
+    "new Date().getTime()",
+    "new Event(\"\").timeStamp",
+    "new File([], \"\").lastModified",
+    "new File([], \"\").lastModifiedDate.getTime()",
+  ];
+  // These are measured in seconds, so we need to scale them up
+  var timeStampCodesDOM = timeStampCodes.concat([
+    "audioContext.currentTime * 1000",
+    "canvasStream.currentTime * 1000",
+  ]);
+
+  let isRounded = (x) => {
+    expectedPrecision = 2;
+    let rounded = (Math.floor(x / expectedPrecision) * expectedPrecision);
+    // First we do the perfectly normal check that should work just fine
+    if (rounded === x || x === 0)
+      return true;
+
+  // When we're diving by non-whole numbers, we may not get perfect
+  // multiplication/division because of floating points.
+  // When dealing with ms since epoch, a double's precision is on the order
+  // of 1/5 of a microsecond, so we use a value a little higher than that as
+  // our epsilon.
+  // To be clear, this error is introduced in our re-calculation of 'rounded'
+  // above in JavaScript.
+  if (Math.abs(rounded - x + expectedPrecision) < .0005) {
+    return true;
+  } else if (Math.abs(rounded - x) < .0005) {
+    return true;
+  }
+
+    // Then we handle the case where you're sub-millisecond and the timer is not
+    // We check that the timer is not sub-millisecond by assuming it is not if it
+    // returns an even number of milliseconds
+    if (expectedPrecision < 1 && Math.round(x) == x) {
+      if (Math.round(rounded) == x) {
+        return true;
+      }
+    }
+
+    ok(false, "Looming Test Failure, Additional Debugging Info: Expected Precision: " + expectedPrecision + " Measured Value: " + x +
+      " Rounded Vaue: " + rounded + " Fuzzy1: " + Math.abs(rounded - x + expectedPrecision) +
+      " Fuzzy 2: " + Math.abs(rounded - x));
+
+    return false;
+  };
+
+  // ================================================================================================
+  // ================================================================================================
+
+  function* checkWorker(worker) {
+    // The child worker will send the results back.
+    let checkWorkerTimeStamps = () => new Promise(function(resolve) {
+      let onMessage = function(event) {
+        worker.removeEventListener("message", onMessage);
+
+        let timeStamps = event.data;
+        for (let i = 0; i < timeStampCodes.length; i++) {
+          let timeStamp = timeStamps[i];
+          ok(isRounded(timeStamp),
+             "'" + timeStampCodes[i] +
+             "' should be rounded to nearest 2 ms in workers; saw " +
+             timeStamp);
+        }
+        resolve();
+      };
+      worker.addEventListener("message", onMessage);
+    });
+
+    // Send the codes to its child worker.
+    worker.postMessage(timeStampCodes);
+
+    // First, check the child's results.
+    yield checkWorkerTimeStamps();
+    // Then, check the grandchild's results.
+    yield checkWorkerTimeStamps();
+
+    worker.terminate();
+  }
+
+  add_task(function*() {
+    let worker = new Worker("worker_child.js");
+    // Allow ~550 ms to elapse, so we can get non-zero
+    // time values for all elements.
+    yield new Promise(resolve => window.setTimeout(resolve, 550));
+    yield checkWorker(worker);
+  });
+
+  // ================================================================================================
+  // ================================================================================================
+
+
+  add_task(function*() {
+    // Loop through each timeStampCode, evaluate it,
+    // and check if it is rounded
+    for (let timeStampCode of timeStampCodesDOM) {
+      let timeStamp = eval(timeStampCode);
+      ok(isRounded(timeStamp),
+         "'" + timeStampCode + "' should be rounded to nearest 2ms" +
+         " saw " + timeStamp);
+    }
+  });
+
+</script>
+
+
+</body>
+</html>
new file mode 100755
--- /dev/null
+++ b/dom/tests/mochitest/general/worker_child.js
@@ -0,0 +1,28 @@
+let timeStampCodes;
+let worker = new Worker("worker_grandchild.js");
+
+function listenToParent(event) {
+  self.removeEventListener("message", listenToParent);
+  timeStampCodes = event.data;
+
+  let timeStamps = [];
+  for (let timeStampCode of timeStampCodes) {
+    timeStamps.push(eval(timeStampCode));
+  }
+  // Send the timeStamps to the parent.
+  postMessage(timeStamps);
+
+  // Tell the grandchild to start.
+  worker.postMessage(timeStampCodes);
+}
+
+// The worker grandchild will send results back.
+function listenToChild(event) {
+  worker.removeEventListener("message", listenToChild);
+  // Pass the results to the parent.
+  postMessage(event.data);
+  worker.terminate();
+}
+
+worker.addEventListener("message", listenToChild);
+self.addEventListener("message", listenToParent);
new file mode 100755
--- /dev/null
+++ b/dom/tests/mochitest/general/worker_grandchild.js
@@ -0,0 +1,10 @@
+self.addEventListener("message", function(event) {
+  let timeStampCodes = event.data;
+
+  let timeStamps = [];
+  for (let timeStampCode of timeStampCodes) {
+    timeStamps.push(eval(timeStampCode));
+  }
+  // Send the timeStamps to the parent.
+  postMessage(timeStamps);
+});