Bug 1217238 - Regression tests for reducing precision of time exposed by Javascript.
This patch is adapted from Tor
bug 1517.
Test that the following javascript codes will return timeStamps that are rounded
to 100ms.
performance.now()
new Date().getTime()
new Event("").timeStamp
new File([], "").lastModified
new File([], "").lastModifiedDate.getTime()
audioContext.currentTime * 1000
canvasStream.currentTime * 1000
video.currentTime * 1000
audio.currentTime * 1000
The first five codes are also tested in simple workers and nested workers,
created before and after the pref is on, to ensure that the pref has correctly
propagated.
MozReview-Commit-ID: CuoxmGRrBnm
--- a/browser/components/resistfingerprinting/moz.build
+++ b/browser/components/resistfingerprinting/moz.build
@@ -5,8 +5,12 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files("**"):
BUG_COMPONENT = ("Core", "Security")
BROWSER_CHROME_MANIFESTS += [
'test/browser/browser.ini',
]
+
+MOCHITEST_MANIFESTS += [
+ 'test/mochitest/mochitest.ini',
+]
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/.eslintrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+ "rules": {
+ "no-eval": "off"
+ },
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ worker_child.js
+ worker_grandchild.js
+
+[test_reduce_time_precision.html]
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tor bug
+https://trac.torproject.org/projects/tor/ticket/1517
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Tor Bug 1517</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://trac.torproject.org/projects/tor/ticket/1517">Tor Bug 1517</a>
+
+<!-- Canvas for testing 'currentTime' -->
+<canvas id="test-canvas" width="100" height="100"></canvas>
+
+<!-- The main testing script -->
+<script type="application/javascript;version=1.7">
+ SimpleTest.requestFlakyTimeout("testing JS time-based fingerprinting");
+
+ // Prepare for test of AudioContext.currentTime
+ let audioContext = new AudioContext();
+ // Prepare for test of CanvasStream.currentTime
+ let canvas = document.getElementById("test-canvas");
+ let context = canvas.getContext('2d');
+ context.fillText("test", 20, 20);
+ let canvasStream = canvas.captureStream(25);
+
+ // Known ways to generate time stamps, in milliseconds
+ const timeStampCodes = [
+ 'performance.now()',
+ 'new Date().getTime()',
+ 'new Event("").timeStamp',
+ 'new File([], "").lastModified',
+ 'new File([], "").lastModifiedDate.getTime()',
+ ];
+
+ const kExpectedResolution = 100;
+
+ function* checkWorker(worker) {
+ // The child worker will send the results back.
+ let checkTimeStamps = () => new Promise(function(resolve) {
+ let onMessage = function(event) {
+ worker.removeEventListener("message", onMessage);
+
+ let timeStamps = event.data;
+ for (let i = 0; i < timeStampCodes.length; i++) {
+ let timeStamp = timeStamps[i];
+ is(timeStamp % kExpectedResolution, 0,
+ "'" + timeStampCodes[i] +
+ "' should be rounded to nearest 100 ms in workers; saw " +
+ timeStamp);
+ }
+ resolve();
+ };
+ worker.addEventListener("message", onMessage);
+ });
+
+ // Send the codes to its child worker.
+ worker.postMessage(timeStampCodes);
+
+ // First, check the child's results.
+ yield checkTimeStamps();
+ // Then, check the grandchild's results.
+ yield checkTimeStamps();
+
+ worker.terminate();
+ }
+
+ add_task(function* testWorker() {
+ // Create one worker before setting the pref, and one after, in order to
+ // check that the resolution is updated whether or not the worker was
+ // already started
+ let worker1 = new Worker("worker_child.js");
+ yield SpecialPowers.pushPrefEnv({
+ "set": [["privacy.resistFingerprinting", true]]});
+ let worker2 = new Worker("worker_child.js");
+ // Allow ~550 ms to elapse, so we can get non-zero
+ // time values for all elements.
+ yield new Promise(resolve => window.setTimeout(resolve, 550));
+ yield checkWorker(worker1);
+ yield checkWorker(worker2);
+ });
+
+ add_task(function* testDOM() {
+ let timeStampCodesDOM = timeStampCodes.concat([
+ 'audioContext.currentTime * 1000',
+ 'canvasStream.currentTime * 1000',
+ ]);
+ // Loop through each timeStampCode, evaluate it,
+ // and check if it is rounded to the nearest 100 ms.
+ for (let timeStampCode of timeStampCodesDOM) {
+ let timeStamp = eval(timeStampCode);
+ is(timeStamp % kExpectedResolution, 0,
+ "'" + timeStampCode +
+ "' should be rounded to nearest 100 ms; saw " +
+ timeStamp);
+ }
+ });
+
+</script>
+
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/worker_child.js
@@ -0,0 +1,28 @@
+let timeStampCodes;
+let worker = new Worker("worker_grandchild.js");
+
+function listenToParent(event) {
+ self.removeEventListener("message", listenToParent);
+ timeStampCodes = event.data;
+
+ let timeStamps = [];
+ for (let timeStampCode of timeStampCodes) {
+ timeStamps.push(eval(timeStampCode));
+ }
+ // Send the timeStamps to the parent.
+ postMessage(timeStamps);
+
+ // Tell the grandchild to start.
+ worker.postMessage(timeStampCodes);
+}
+
+// The worker grandchild will send results back.
+function listenToChild(event) {
+ worker.removeEventListener("message", listenToChild);
+ // Pass the results to the parent.
+ postMessage(event.data);
+ worker.terminate();
+}
+
+worker.addEventListener("message", listenToChild);
+self.addEventListener("message", listenToParent);
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/worker_grandchild.js
@@ -0,0 +1,10 @@
+self.addEventListener("message", function(event) {
+ let timeStampCodes = event.data;
+
+ let timeStamps = [];
+ for (let timeStampCode of timeStampCodes) {
+ timeStamps.push(eval(timeStampCode));
+ }
+ // Send the timeStamps to the parent.
+ postMessage(timeStamps);
+});