Bug 1424341 Add tests for dynamically tuning the timer precision draft
authorTom Ritter <tom@mozilla.com>
Tue, 09 Jan 2018 15:10:59 -0600
changeset 718837 144429b6a7f5573d8b0e0ddaad1b22da3afe707f
parent 718836 dac8763669c1c6f0068f527edff612baa010bc8b
child 718838 288c31e88fcab605619a8d6e9220b9168c8a2507
push id95052
push userbmo:tom@mozilla.com
push dateWed, 10 Jan 2018 21:56:43 +0000
bugs1424341
milestone59.0a1
Bug 1424341 Add tests for dynamically tuning the timer precision 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",