1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
<!DOCTYPE HTML>
<html>
<!--
Tor bug
https://trac.torproject.org/projects/tor/ticket/1517
-->
<head>
<meta charset="utf-8">
<title>Test for Tor Bug 1517 and Mozilla Bug 1424341</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://trac.torproject.org/projects/tor/ticket/1517">Tor Bug 1517</a>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1424341">Mozilla Bug 1424341</a>
<!-- Canvas for testing 'currentTime' -->
<canvas id="test-canvas" width="100" height="100"></canvas>
<!-- The main testing script -->
<script type="application/javascript">
SimpleTest.requestFlakyTimeout("testing JS time-based fingerprinting");
// Prepare for test of AudioContext.currentTime
let audioContext = new AudioContext();
// Prepare for test of CanvasStream.currentTime
let canvas = document.getElementById("test-canvas");
let context = canvas.getContext("2d");
context.fillText("test", 20, 20);
let canvasStream = canvas.captureStream(25);
// Known ways to generate time stamps, in milliseconds
const timeStampCodes = [
"performance.now()",
"new Date().getTime()",
"new Event(\"\").timeStamp",
"new File([], \"\").lastModified",
"new File([], \"\").lastModifiedDate.getTime()",
];
// These are measured in seconds, so we need to scale them up
var timeStampCodesDOM = timeStampCodes.concat([
"audioContext.currentTime * 1000",
"canvasStream.currentTime * 1000",
]);
let isRounded = (x) => {
expectedPrecision = 2;
let rounded = (Math.floor(x / expectedPrecision) * expectedPrecision);
// First we do the perfectly normal check that should work just fine
if (rounded === x || x === 0)
return true;
// When we're diving by non-whole numbers, we may not get perfect
// multiplication/division because of floating points.
// When dealing with ms since epoch, a double's precision is on the order
// of 1/5 of a microsecond, so we use a value a little higher than that as
// our epsilon.
// To be clear, this error is introduced in our re-calculation of 'rounded'
// above in JavaScript.
if (Math.abs(rounded - x + expectedPrecision) < .0005) {
return true;
} else if (Math.abs(rounded - x) < .0005) {
return true;
}
// Then we handle the case where you're sub-millisecond and the timer is not
// We check that the timer is not sub-millisecond by assuming it is not if it
// returns an even number of milliseconds
if (expectedPrecision < 1 && Math.round(x) == x) {
if (Math.round(rounded) == x) {
return true;
}
}
ok(false, "Looming Test Failure, Additional Debugging Info: Expected Precision: " + expectedPrecision + " Measured Value: " + x +
" Rounded Vaue: " + rounded + " Fuzzy1: " + Math.abs(rounded - x + expectedPrecision) +
" Fuzzy 2: " + Math.abs(rounded - x));
return false;
};
// ================================================================================================
// ================================================================================================
function* checkWorker(worker) {
// The child worker will send the results back.
let checkWorkerTimeStamps = () => new Promise(function(resolve) {
let onMessage = function(event) {
worker.removeEventListener("message", onMessage);
let timeStamps = event.data;
for (let i = 0; i < timeStampCodes.length; i++) {
let timeStamp = timeStamps[i];
ok(isRounded(timeStamp),
"'" + timeStampCodes[i] +
"' should be rounded to nearest 2 ms in workers; saw " +
timeStamp);
}
resolve();
};
worker.addEventListener("message", onMessage);
});
// Send the codes to its child worker.
worker.postMessage(timeStampCodes);
// First, check the child's results.
yield checkWorkerTimeStamps();
// Then, check the grandchild's results.
yield checkWorkerTimeStamps();
worker.terminate();
}
add_task(function*() {
let worker = new Worker("worker_child.js");
// Allow ~550 ms to elapse, so we can get non-zero
// time values for all elements.
yield new Promise(resolve => window.setTimeout(resolve, 550));
yield checkWorker(worker);
});
// ================================================================================================
// ================================================================================================
add_task(function*() {
// Loop through each timeStampCode, evaluate it,
// and check if it is rounded
for (let timeStampCode of timeStampCodesDOM) {
let timeStamp = eval(timeStampCode);
ok(isRounded(timeStamp),
"'" + timeStampCode + "' should be rounded to nearest 2ms" +
" saw " + timeStamp);
}
});
</script>
</body>
</html>
|