Bug 1217238 - Regression tests for reducing precision of time exposed by Javascript. draft
authorJonathan Hao <jhao@mozilla.com>
Tue, 06 Jun 2017 11:45:13 +0800
changeset 591427 9327ff05097fa2877ed0d5e9da1b42244cf7b2b3
parent 589301 2c6289f56812c30254acfdddabcfec1e149c0336
child 591428 09e60b4a6e70c57f927309b9497a0188feef38e8
push id63055
push userbmo:jhao@mozilla.com
push dateFri, 09 Jun 2017 02:56:03 +0000
bugs1217238, 1517
milestone55.0a1
Bug 1217238 - Regression tests for reducing precision of time exposed by Javascript. This patch is adapted from Tor bug 1517. Test that the following javascript codes will return timeStamps that are rounded to 100ms. performance.now() new Date().getTime() new Event("").timeStamp new File([], "").lastModified new File([], "").lastModifiedDate.getTime() audioContext.currentTime * 1000 canvasStream.currentTime * 1000 video.currentTime * 1000 audio.currentTime * 1000 The first five codes are also tested in simple workers and nested workers, created before and after the pref is on, to ensure that the pref has correctly propagated. MozReview-Commit-ID: CuoxmGRrBnm
browser/components/resistfingerprinting/moz.build
browser/components/resistfingerprinting/test/mochitest/.eslintrc.js
browser/components/resistfingerprinting/test/mochitest/mochitest.ini
browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
browser/components/resistfingerprinting/test/mochitest/worker_child.js
browser/components/resistfingerprinting/test/mochitest/worker_grandchild.js
--- a/browser/components/resistfingerprinting/moz.build
+++ b/browser/components/resistfingerprinting/moz.build
@@ -5,8 +5,12 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "Security")
 
 BROWSER_CHROME_MANIFESTS += [
     'test/browser/browser.ini',
 ]
+
+MOCHITEST_MANIFESTS += [
+    'test/mochitest/mochitest.ini',
+]
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/.eslintrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+  "rules": {
+    "no-eval": "off"
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  worker_child.js
+  worker_grandchild.js
+
+[test_reduce_time_precision.html]
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tor bug
+https://trac.torproject.org/projects/tor/ticket/1517
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Tor Bug 1517</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>
+
+<!-- Canvas for testing 'currentTime' -->
+<canvas id="test-canvas" width="100" height="100"></canvas>
+
+<!-- The main testing script -->
+<script type="application/javascript;version=1.7">
+  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()',
+  ];
+
+  const kExpectedResolution = 100;
+
+  function* checkWorker(worker) {
+    // The child worker will send the results back.
+    let checkTimeStamps = () => 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];
+          is(timeStamp % kExpectedResolution, 0,
+             "'" + timeStampCodes[i] +
+             "' should be rounded to nearest 100 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 checkTimeStamps();
+    // Then, check the grandchild's results.
+    yield checkTimeStamps();
+
+    worker.terminate();
+  }
+
+  add_task(function* testWorker() {
+    // Create one worker before setting the pref, and one after, in order to
+    // check that the resolution is updated whether or not the worker was
+    // already started
+    let worker1 = new Worker("worker_child.js");
+    yield SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", true]]});
+    let worker2 = 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(worker1);
+    yield checkWorker(worker2);
+  });
+
+  add_task(function* testDOM() {
+    let timeStampCodesDOM = timeStampCodes.concat([
+      'audioContext.currentTime * 1000',
+      'canvasStream.currentTime * 1000',
+    ]);
+    // Loop through each timeStampCode, evaluate it,
+    // and check if it is rounded to the nearest 100 ms.
+    for (let timeStampCode of timeStampCodesDOM) {
+      let timeStamp = eval(timeStampCode);
+      is(timeStamp % kExpectedResolution, 0,
+         "'" + timeStampCode +
+         "' should be rounded to nearest 100 ms; saw " +
+         timeStamp);
+    }
+  });
+
+</script>
+
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/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 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/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);
+});