Bug 1424341 Add tests for dynamically tuning the timer precision r=bkelly,timhuang
authorTom Ritter <tom@mozilla.com>
Tue, 09 Jan 2018 15:10:59 -0600
changeset 452922 ab399bd8412b47486dc8f10f2a647ed247ea675d
parent 452921 1382e00b5726e7b56ec392ce32830ed42c305133
child 452923 4a6035d8a0399473c8acbb4bb376a5a2c3793a04
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly, timhuang
bugs1424341
milestone59.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 1424341 Add tests for dynamically tuning the timer precision r=bkelly,timhuang MozReview-Commit-ID: IM52HhGY7y
browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js
browser/components/resistfingerprinting/test/browser/file_workerPerformance.js
browser/components/resistfingerprinting/test/mochitest/file_animation_api.html
browser/components/resistfingerprinting/test/mochitest/test_animation_api.html
browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
dom/ipc/ContentPrefs.cpp
--- a/browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js
+++ b/browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js
@@ -25,140 +25,266 @@ const PERFORMANCE_TIMINGS = [
   "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(async function runRPTests() {
-  await SpecialPowers.pushPrefEnv({"set":
-    //Run one set of tests with both true to confirm p.rP overrides p.rTP
-    [["privacy.resistFingerprinting", true],
-     ["privacy.reduceTimerPrecision", true]]
-  });
-
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser, TEST_PATH + "file_dummy.html");
 
-  await ContentTask.spawn(tab.linkedBrowser, PERFORMANCE_TIMINGS, async function(list) {
-    const isRounded = x => (Math.floor(x / 100) * 100) === x;
+  let runTests = async function(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);
 
-    ok(isRounded(content.performance.timeOrigin), `For resistFingerprinting, performance.timeOrigin is not correctly rounded: ` + content.performance.timeOrigin);
+    ok(isRounded(content.performance.timeOrigin, expectedPrecision), `For resistFingerprinting, performance.timeOrigin is not correctly rounded: ` + content.performance.timeOrigin);
 
     // Check that whether the performance timing API is correctly spoofed.
-    for (let time of list) {
+    for (let time of timerlist) {
       is(content.performance.timing[time], 0, `For resistFingerprinting, the timing(${time}) is not correctly spoofed.`);
     }
 
     // Try to add some entries.
     content.performance.mark("Test");
     content.performance.mark("Test-End");
     content.performance.measure("Test-Measure", "Test", "Test-End");
 
     // Check that no entries for performance.getEntries/getEntriesByType/getEntriesByName.
     is(content.performance.getEntries().length, 0, "For resistFingerprinting, there should be no entries for performance.getEntries()");
     is(content.performance.getEntriesByType("resource").length, 0, "For resistFingerprinting, there should be no entries for performance.getEntriesByType()");
     is(content.performance.getEntriesByName("Test", "mark").length, 0, "For resistFingerprinting, there should be no entries for performance.getEntriesByName()");
 
+  };
+
+  let expectedPrecision = 100;
+  await SpecialPowers.pushPrefEnv({"set":
+    // Run one set of tests with both true to confirm p.rP overrides p.rTP
+    [["privacy.resistFingerprinting", true],
+     ["privacy.reduceTimerPrecision", true],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+     ]
   });
+  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
+
+  expectedPrecision = 13;
+  await SpecialPowers.pushPrefEnv({"set":
+    // Run one set of tests with both true to confirm p.rP overrides p.rTP
+    [["privacy.resistFingerprinting", true],
+     ["privacy.reduceTimerPrecision", true],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+     ]
+  });
+  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
+
+  expectedPrecision = .13;
+  await SpecialPowers.pushPrefEnv({"set":
+    // Run one set of tests with both true to confirm p.rP overrides p.rTP
+    [["privacy.resistFingerprinting", true],
+     ["privacy.reduceTimerPrecision", true],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+     ]
+  });
+  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
 
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function runRPTestsForWorker() {
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", true],
-     ["privacy.reduceTimerPrecision", false]]
-  });
-
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser, TEST_PATH + "file_dummy.html");
 
-  await ContentTask.spawn(tab.linkedBrowser, null, async function() {
+  let runTest = async function(expectedPrecision) {
     await 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") {
           resolve();
         } else {
           ok(false, "Unknown message type");
           resolve();
         }
       };
-      worker.postMessage({type: "runRPTests"});
+      worker.postMessage({type: "runRPTests", precision: expectedPrecision});
     });
+  };
+
+  let expectedPrecision = 100;
+    // Run one set of tests with both true to confirm p.rP overrides p.rTP
+  await SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", true],
+     ["privacy.reduceTimerPrecision", true],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
   });
+  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
+
+  expectedPrecision = 13;
+  await SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", true],
+     ["privacy.reduceTimerPrecision", false],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
+  });
+  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
+
+  expectedPrecision = .13;
+  await SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", true],
+     ["privacy.reduceTimerPrecision", false],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
+  });
+  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
 
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function runRTPTests() {
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", false],
-     ["privacy.reduceTimerPrecision", true]]
-  });
-
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser, TEST_PATH + "file_dummy.html");
 
-  await ContentTask.spawn(tab.linkedBrowser, PERFORMANCE_TIMINGS, async function(list) {
-    const isRounded = x => (Math.floor(x / 100) * 100) === x;
+  let runTests = async function(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);
 
-    ok(isRounded(content.performance.timeOrigin), `For reduceTimerPrecision, performance.timeOrigin is not correctly rounded: ` + content.performance.timeOrigin);
+    ok(isRounded(content.performance.timeOrigin, expectedPrecision), `For reduceTimerPrecision, performance.timeOrigin is not correctly rounded: ` + content.performance.timeOrigin);
 
     // Check that whether the performance timing API is correctly spoofed.
-    for (let time of list) {
-      ok(isRounded(content.performance.timing[time]), `For reduceTimerPrecision, the timing(${time}) is not correctly rounded.`);
+    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 that no entries for performance.getEntries/getEntriesByType/getEntriesByName.
+    // Check the entries for performance.getEntries/getEntriesByType/getEntriesByName.
     is(content.performance.getEntries().length, 4, "For reduceTimerPrecision, there should be 4 entries for performance.getEntries()");
-    for(var i=0; i<4; i++) {
+    for (var i = 0; i < 4; i++) {
       let startTime = content.performance.getEntries()[i].startTime;
       let duration = content.performance.getEntries()[i].duration;
-      ok(isRounded(startTime), "For reduceTimerPrecision, performance.getEntries(" + i.toString() + ").startTime is not rounded: " + startTime.toString());
-      ok(isRounded(duration), "For reduceTimerPrecision, performance.getEntries(" + i.toString() + ").duration is not rounded: " + duration.toString());
+      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();
+  };
 
+  let expectedPrecision = 100;
+  await SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", false],
+     ["privacy.reduceTimerPrecision", true],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+    ]
+    });
+  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
+
+  expectedPrecision = 13;
+  await SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", false],
+     ["privacy.reduceTimerPrecision", true],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+    ]
   });
+  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
+
+  expectedPrecision = .13;
+  await SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", false],
+     ["privacy.reduceTimerPrecision", true],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+    ]
+  });
+  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
 
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function runRTPTestsForWorker() {
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", false],
-     ["privacy.reduceTimerPrecision", true]]
-  });
-
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser, TEST_PATH + "file_dummy.html");
 
-  await ContentTask.spawn(tab.linkedBrowser, null, async function() {
+  let runTest = async function(expectedPrecision) {
     await 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") {
           resolve();
         } else {
           ok(false, "Unknown message type");
           resolve();
         }
       };
-      worker.postMessage({type: "runRTPTests"});
+      worker.postMessage({type: "runRTPTests", precision: expectedPrecision});
     });
+  };
+
+  let expectedPrecision = 100;
+  await SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", false],
+     ["privacy.reduceTimerPrecision", true],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
   });
+  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
 
+  expectedPrecision = 13;
+  await SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", false],
+     ["privacy.reduceTimerPrecision", true],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
+  });
+  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
+
+  expectedPrecision = .13;
+  await SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", false],
+     ["privacy.reduceTimerPrecision", true],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
+  });
+  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
   await BrowserTestUtils.removeTab(tab);
-});
\ No newline at end of file
+});
--- a/browser/components/resistfingerprinting/test/browser/file_workerPerformance.js
+++ b/browser/components/resistfingerprinting/test/browser/file_workerPerformance.js
@@ -5,57 +5,85 @@ function ok(a, msg) {
 function is(a, b, msg) {
   ok(a === b, msg);
 }
 
 function finish() {
   postMessage({type: "finish"});
 }
 
-function runRPTests() {
-  const isRounded = x => (Math.floor(x / 100) * 100) === x;
-  //ok(isRounded(performance.timeOrigin), `For resistFingerprinting, performance.timeOrigin is not correctly rounded: ` + performance.timeOrigin);
+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 runRPTests(expectedPrecision) {
+  ok(isRounded(performance.timeOrigin, expectedPrecision), `In a worker, for resistFingerprinting, performance.timeOrigin is not correctly rounded: ` + performance.timeOrigin);
 
   // Try to add some entries.
   performance.mark("Test");
   performance.mark("Test-End");
   performance.measure("Test-Measure", "Test", "Test-End");
 
   // Check that no entries for performance.getEntries/getEntriesByType/getEntriesByName.
-  is(performance.getEntries().length, 0, "For resistFingerprinting: No entries for performance.getEntries() for workers");
-  is(performance.getEntriesByType("resource").length, 0, "For resistFingerprinting: No entries for performance.getEntriesByType() for workers");
-  is(performance.getEntriesByName("Test", "mark").length, 0, "For resistFingerprinting: No entries for performance.getEntriesByName() for workers");
+  is(performance.getEntries().length, 0, "In a worker, for resistFingerprinting: No entries for performance.getEntries() for workers");
+  is(performance.getEntriesByType("resource").length, 0, "In a worker, for resistFingerprinting: No entries for performance.getEntriesByType() for workers");
+  is(performance.getEntriesByName("Test", "mark").length, 0, "In a worker, for resistFingerprinting: No entries for performance.getEntriesByName() for workers");
 
   finish();
 }
 
-function runRTPTests() {
-  const isRounded = x => (Math.floor(x / 100) * 100) === x;
-  //ok(isRounded(performance.timeOrigin), `For reduceTimerPrecision, performance.timeOrigin is not correctly rounded: ` + performance.timeOrigin);
+function runRTPTests(expectedPrecision) {
+  ok(isRounded(performance.timeOrigin, expectedPrecision), `In a worker, for reduceTimerPrecision, performance.timeOrigin is not correctly rounded: ` + performance.timeOrigin);
 
   // Try to add some entries.
   performance.mark("Test");
   performance.mark("Test-End");
   performance.measure("Test-Measure", "Test", "Test-End");
 
-  // Check that no entries for performance.getEntries/getEntriesByType/getEntriesByName.
-  is(performance.getEntries().length, 3, "For reduceTimerPrecision: Incorrect number of entries for performance.getEntries() for workers: " + performance.getEntries().length);
-  for(var i=0; i<3; i++) {
+  // 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), "For reduceTimerPrecision, performance.getEntries(" + i.toString() + ").startTime is not rounded: " + startTime.toString());
-    ok(isRounded(duration), "For reduceTimerPrecision, performance.getEntries(" + i.toString() + ").duration is not rounded: " + duration.toString());
+    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, "For reduceTimerPrecision: Incorrect number of entries for performance.getEntriesByType() for workers: " + performance.getEntriesByType("resource").length);
-  is(performance.getEntriesByName("Test", "mark").length, 1, "For reduceTimerPrecision: Incorrect number of entries for performance.getEntriesByName() for workers: " + performance.getEntriesByName("Test", "mark").length);
+  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) {
   if (e.data.type === "runRPTests") {
-    runRPTests();
+    runRPTests(e.data.precision);
   } else if (e.data.type === "runRTPTests") {
-    runRTPTests();
+    runRTPTests(e.data.precision);
   } else {
     ok(false, "Unknown message type");
   }
 };
--- a/browser/components/resistfingerprinting/test/mochitest/file_animation_api.html
+++ b/browser/components/resistfingerprinting/test/mochitest/file_animation_api.html
@@ -23,33 +23,62 @@
         moveOn();
       }
       tries++;
     }, 100);
     var moveOn = () => { clearInterval(interval); aCallback(); };
   }
 
   function runTest() {
-    const isRounded = x => (Math.floor(x / 100) * 100) === x;
+    let expectedPrecision = opener.expectedPrecision / 1000;
+    let isRounded = (x) => {
+      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;
+    };
     const testDiv = document.getElementById("testDiv");
     const animation = testDiv.animate({ opacity: [0, 1] }, 100000);
     animation.play();
 
     waitForCondition(
       () => animation.currentTime > 100,
         () => {
           opener.ok(isRounded(animation.startTime),
-             "pref: " + opener.currentPref + " - animation.startTime is not rounded");
+             "pref: " + opener.currentPref + " - animation.startTime with precision " + expectedPrecision + " is not rounded: " + animation.startTime);
           opener.ok(isRounded(animation.currentTime),
-             "pref: " + opener.currentPref + " - animation.currentTime is not rounded");
+             "pref: " + opener.currentPref + " - animation.currentTime with precision " + expectedPrecision + " is not rounded: " + animation.currentTime);
           opener.ok(isRounded(animation.timeline.currentTime),
-             "pref: " + opener.currentPref + " - animation.timeline.currentTime is not rounded");
+             "pref: " + opener.currentPref + " - animation.timeline.currentTime with precision " + expectedPrecision + " is not rounded: " + animation.timeline.currentTime);
           if (document.timeline) {
             opener.ok(isRounded(document.timeline.currentTime),
-               "pref: " + opener.currentPref + " - document.timeline.currentTime is not rounded");
+               "pref: " + opener.currentPref + " - document.timeline.currentTime with precision " + expectedPrecision + " is not rounded: " + document.timeline.currentTime);
           }
           opener.done();
           window.close();
         },
         "animation failed to start");
   }
 </script>
 </head>
--- a/browser/components/resistfingerprinting/test/mochitest/test_animation_api.html
+++ b/browser/components/resistfingerprinting/test/mochitest/test_animation_api.html
@@ -9,52 +9,131 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   /** Test for Bug 1382545 **/
   SimpleTest.waitForExplicitFinish();
 
   var currentPref = "";
+  var expectedPrecision = 0;
   window.onload = () => {
     currentPref = "privacy.resistFingerprinting";
+    expectedPrecision = 100000;
     SpecialPowers.pushPrefEnv({"set":
       [
         ["privacy.resistFingerprinting", true],
         ["dom.animations-api.core.enabled", true],
-        ["privacy.reduceTimerPrecision", false]
+        ["privacy.reduceTimerPrecision", false],
+        ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
       ]
     }, runTest);
   };
 
   function runTest() {
     window.open("file_animation_api.html");
   }
 
   let completed = 0;
-  const numTests = 2;
+  const numTests = 8;
   function done() {
     completed++;
     if (completed == numTests) {
       SimpleTest.finish();
     } else {
       nextTest();
     }
   }
 
   function nextTest() {
+    // ----------------------------------------------
     if (completed == 1) {
       currentPref = "privacy.reduceTimerPrecision";
+      expectedPrecision = 100000;
+      SpecialPowers.pushPrefEnv({"set":
+        [
+          ["privacy.resistFingerprinting", false],
+          ["dom.animations-api.core.enabled", true],
+          ["privacy.reduceTimerPrecision", true],
+          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
+        ]
+      }, runTest);
+    // ----------------------------------------------
+    } else if (completed == 2) {
+      currentPref = "privacy.resistFingerprinting";
+      expectedPrecision = 50000;
+      SpecialPowers.pushPrefEnv({"set":
+        [
+          ["privacy.resistFingerprinting", true],
+          ["dom.animations-api.core.enabled", true],
+          ["privacy.reduceTimerPrecision", false],
+          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
+        ]
+      }, runTest);
+    // ----------------------------------------------
+    } else if (completed == 3) {
+      currentPref = "privacy.reduceTimerPrecision";
+      expectedPrecision = 50000;
       SpecialPowers.pushPrefEnv({"set":
         [
           ["privacy.resistFingerprinting", false],
           ["dom.animations-api.core.enabled", true],
-          ["privacy.reduceTimerPrecision", true]
+          ["privacy.reduceTimerPrecision", true],
+          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
+        ]
+      }, runTest);
+    // ----------------------------------------------
+    } else if (completed == 4) {
+      currentPref = "privacy.resistFingerprinting";
+      expectedPrecision = 100;
+      SpecialPowers.pushPrefEnv({"set":
+        [
+          ["privacy.resistFingerprinting", true],
+          ["dom.animations-api.core.enabled", true],
+          ["privacy.reduceTimerPrecision", false],
+          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
         ]
       }, runTest);
+    // ----------------------------------------------
+    } else if (completed == 5) {
+      currentPref = "privacy.reduceTimerPrecision";
+      expectedPrecision = 100;
+      SpecialPowers.pushPrefEnv({"set":
+        [
+          ["privacy.resistFingerprinting", false],
+          ["dom.animations-api.core.enabled", true],
+          ["privacy.reduceTimerPrecision", true],
+          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
+        ]
+      }, runTest);
+    // ----------------------------------------------
+    } else if (completed == 6) {
+      currentPref = "privacy.resistFingerprinting";
+      expectedPrecision = 13;
+      SpecialPowers.pushPrefEnv({"set":
+        [
+          ["privacy.resistFingerprinting", true],
+          ["dom.animations-api.core.enabled", true],
+          ["privacy.reduceTimerPrecision", false],
+          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
+        ]
+      }, runTest);
+    // ----------------------------------------------
+    } else if (completed == 7) {
+      currentPref = "privacy.reduceTimerPrecision";
+      expectedPrecision = 13;
+      SpecialPowers.pushPrefEnv({"set":
+        [
+          ["privacy.resistFingerprinting", false],
+          ["dom.animations-api.core.enabled", true],
+          ["privacy.reduceTimerPrecision", true],
+          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
+        ]
+      }, runTest);
+    // ----------------------------------------------
     } else {
       ok(false, "I seem to have asked for " + numTests +
          " tests, but don't know how to run them all.");
       SimpleTest.finish();
     }
   }
   </script>
 </head>
--- a/browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
+++ b/browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
@@ -34,118 +34,268 @@ https://trac.torproject.org/projects/tor
   const timeStampCodes = [
     "performance.now()",
     "new Date().getTime()",
     "new Event(\"\").timeStamp",
     "new File([], \"\").lastModified",
     "new File([], \"\").lastModifiedDate.getTime()",
   ];
 
-  const kExpectedResolution = 100;
+  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;
+    }
 
-  function* checkWorker(worker, prefname) {
+    // 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;
+  };
+
+  async function checkWorker(worker, prefname, expectedPrecision) {
     // The child worker will send the results back.
-    let checkTimeStamps = () => new Promise(function(resolve) {
+    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];
-          is(timeStamp % kExpectedResolution, 0,
+          ok(isRounded(timeStamp, expectedPrecision),
             "pref: " + prefname + " - '" +
              "'" + timeStampCodes[i] +
-             "' should be rounded to nearest 100 ms in workers; saw " +
+             "' should be rounded to nearest " + expectedPrecision + " us 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();
+    await checkWorkerTimeStamps();
     // Then, check the grandchild's results.
-    yield checkTimeStamps();
+    await checkWorkerTimeStamps();
 
     worker.terminate();
   }
 
-  add_task(async function testWorkerRFP() {
-    // 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");
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", true],
-              ["privacy.reduceTimerPrecision", false]]});
-    let worker2 = new Worker("worker_child.js");
-    // Allow ~550 ms to elapse, so we can get non-zero
-    // time values for all elements.
-    await new Promise(resolve => window.setTimeout(resolve, 550));
-    await checkWorker(worker1, "privacy.resistFingerprinting");
-    await checkWorker(worker2, "privacy.resistFingerprinting");
-  });
-
-  add_task(async function testWorkerRTP() {
-    // 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");
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", false],
-              ["privacy.reduceTimerPrecision", true]]});
-    let worker2 = new Worker("worker_child.js");
-    // Allow ~550 ms to elapse, so we can get non-zero
-    // time values for all elements.
-    await new Promise(resolve => window.setTimeout(resolve, 550));
-    await checkWorker(worker1, "privacy.reduceTimerPrecision");
-    await checkWorker(worker2, "privacy.reduceTimerPrecision");
-  });
-
-  add_task(async function testDOMRFP() {
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", true],
-              ["privacy.reduceTimerPrecision", false]]});
+  function checkTimestamps(pref, expectedPrecision) {
+    // These are measured in seconds, so we need to scale them up
     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.
+    // and check if it is rounded
     for (let timeStampCode of timeStampCodesDOM) {
       let timeStamp = eval(timeStampCode);
-      is(timeStamp % kExpectedResolution, 0,
-        "pref: privacy.resistFingerprinting - '" +
+      ok(isRounded(timeStamp, expectedPrecision),
+        "pref: " + pref + " - '" +
          "'" + timeStampCode +
-         "' should be rounded to nearest 100 ms; saw " +
+         "' should be rounded to nearest " +
+         expectedPrecision + " us; saw " +
          timeStamp);
     }
+  }
+
+
+  add_task(async function testWorkerRFP1() {
+    // 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 expectedPrecision = 100;
+    let worker1 = new Worker("worker_child.js");
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", true],
+              ["privacy.reduceTimerPrecision", false],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    let worker2 = new Worker("worker_child.js");
+    // Allow ~550 ms to elapse, so we can get non-zero
+    // time values for all elements.
+    await new Promise(resolve => window.setTimeout(resolve, 550));
+    await checkWorker(worker1, "privacy.resistFingerprinting", expectedPrecision);
+    await checkWorker(worker2, "privacy.resistFingerprinting", expectedPrecision);
   });
 
-  add_task(async function testDOMRTP() {
+  add_task(async function testWorkerRFP2() {
+    // 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 expectedPrecision = 13;
+    let worker1 = new Worker("worker_child.js");
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", true],
+              ["privacy.reduceTimerPrecision", false],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    let worker2 = new Worker("worker_child.js");
+    // Allow ~550 ms to elapse, so we can get non-zero
+    // time values for all elements.
+    await new Promise(resolve => window.setTimeout(resolve, 550));
+    await checkWorker(worker1, "privacy.resistFingerprinting", expectedPrecision);
+    await checkWorker(worker2, "privacy.resistFingerprinting", expectedPrecision);
+  });
+
+  add_task(async function testWorkerRFP3() {
+    // 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 expectedPrecision = .13;
+    let worker1 = new Worker("worker_child.js");
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", true],
+              ["privacy.reduceTimerPrecision", false],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    let worker2 = new Worker("worker_child.js");
+    // Allow ~550 ms to elapse, so we can get non-zero
+    // time values for all elements.
+    await new Promise(resolve => window.setTimeout(resolve, 550));
+    await checkWorker(worker1, "privacy.resistFingerprinting", expectedPrecision);
+    await checkWorker(worker2, "privacy.resistFingerprinting", expectedPrecision);
+  });
+
+  add_task(async function testWorkerRTP1() {
+    // 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 expectedPrecision = 100;
+    let worker1 = new Worker("worker_child.js");
     await SpecialPowers.pushPrefEnv({
       "set": [["privacy.resistFingerprinting", false],
-              ["privacy.reduceTimerPrecision", true]]});
-    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,
-         "pref: privacy.reduceTimerPrecision - '" +
-         timeStampCode +
-         "' should be rounded to nearest 100 ms; saw " +
-         timeStamp);
-    }
+              ["privacy.reduceTimerPrecision", true],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    let worker2 = new Worker("worker_child.js");
+    // Allow ~550 ms to elapse, so we can get non-zero
+    // time values for all elements.
+    await new Promise(resolve => window.setTimeout(resolve, 550));
+    await checkWorker(worker1, "privacy.reduceTimerPrecision", expectedPrecision);
+    await checkWorker(worker2, "privacy.reduceTimerPrecision", expectedPrecision);
+  });
+
+  add_task(async function testWorkerRTP2() {
+    // 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 expectedPrecision = 13;
+    let worker1 = new Worker("worker_child.js");
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", false],
+              ["privacy.reduceTimerPrecision", true],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    let worker2 = new Worker("worker_child.js");
+    // Allow ~550 ms to elapse, so we can get non-zero
+    // time values for all elements.
+    await new Promise(resolve => window.setTimeout(resolve, 550));
+    await checkWorker(worker1, "privacy.reduceTimerPrecision", expectedPrecision);
+    await checkWorker(worker2, "privacy.reduceTimerPrecision", expectedPrecision);
+  });
+
+  add_task(async function testWorkerRTP3() {
+    // 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 expectedPrecision = .13;
+    let worker1 = new Worker("worker_child.js");
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", false],
+              ["privacy.reduceTimerPrecision", true],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    let worker2 = new Worker("worker_child.js");
+    // Allow ~550 ms to elapse, so we can get non-zero
+    // time values for all elements.
+    await new Promise(resolve => window.setTimeout(resolve, 550));
+    await checkWorker(worker1, "privacy.reduceTimerPrecision", expectedPrecision);
+    await checkWorker(worker2, "privacy.reduceTimerPrecision", expectedPrecision);
+  });
+
+  add_task(async function testDOMRFP1() {
+    let expectedPrecision = 100;
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", true],
+              ["privacy.reduceTimerPrecision", false],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    checkTimestamps("privacy.resistFingerprinting", expectedPrecision);
+  });
+
+  add_task(async function testDOMRFP2() {
+    let expectedPrecision = 13;
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", true],
+              ["privacy.reduceTimerPrecision", false],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    checkTimestamps("privacy.resistFingerprinting", expectedPrecision);
+  });
+
+  add_task(async function testDOMRFP3() {
+    let expectedPrecision = .13;
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", true],
+              ["privacy.reduceTimerPrecision", false],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    checkTimestamps("privacy.resistFingerprinting", expectedPrecision);
+  });
+
+  add_task(async function testDOMRTP1() {
+    let expectedPrecision = 100;
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", false],
+              ["privacy.reduceTimerPrecision", true],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    checkTimestamps("privacy.reduceTimerPrecision", expectedPrecision);
+  });
+
+  add_task(async function testDOMRTP2() {
+    let expectedPrecision = 13;
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", false],
+              ["privacy.reduceTimerPrecision", true],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    checkTimestamps("privacy.reduceTimerPrecision", expectedPrecision);
+  });
+
+  add_task(async function testDOMRTP3() {
+    let expectedPrecision = .13;
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", false],
+              ["privacy.reduceTimerPrecision", true],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+    checkTimestamps("privacy.reduceTimerPrecision", expectedPrecision);
   });
 
 </script>
 
 
 </body>
 </html>
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -281,18 +281,18 @@ const char* mozilla::dom::ContentPrefs::
   "network.tcp.keepalive.probe_count",
   "network.tcp.keepalive.retry_interval",
   "network.tcp.sendbuffer",
   "nglayout.debug.invalidation",
   "privacy.donottrackheader.enabled",
   "privacy.firstparty.isolate",
   "privacy.firstparty.isolate.restrict_opener_access",
   "privacy.reduceTimerPrecision",
-  "privacy.reduceTimerPrecision.microseconds",
   "privacy.resistFingerprinting",
+  "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
   "privacy.resistFingerprinting.target_video_res",
   "privacy.resistFingerprinting.video_dropped_ratio",
   "privacy.resistFingerprinting.video_frames_per_sec",
   "privacy.trackingprotection.lower_network_priority",
   "privacy.window.maxInnerHeight",
   "privacy.window.maxInnerWidth",
   "security.csp.enable",
   "security.data_uri.block_toplevel_data_uri_navigations",