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
|
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="mediaStreamPlayback.js"></script>
</head>
<body>
<script>
/* global SimpleTest SpecialPowers */
async function testEnumerateDevices(expectDevices) {
let devices = await navigator.mediaDevices.enumerateDevices();
if (!expectDevices) {
SimpleTest.is(devices.length, 0, "testEnumerateDevices: No devices");
return;
}
let cams = devices.filter((device) => device.kind == "videoinput");
let mics = devices.filter((device) => device.kind == "audioinput");
SimpleTest.ok((cams.length == 1) && (mics.length == 1),
"testEnumerateDevices: a microphone and a camera");
}
async function testGetUserMedia(expectDevices) {
const constraints = [
{audio: true},
{video: true},
{audio: true, video: true},
{video: {width: {min: 1e9}}}, // impossible
{audio: {channelCount: {exact: 1e3}}}, // impossible
];
for (let constraint of constraints) {
let message = "getUserMedia(" + JSON.stringify(constraint) + ")";
try {
let stream = await navigator.mediaDevices.getUserMedia(constraint);
SimpleTest.ok(expectDevices, message + " resolved");
if (!expectDevices) {
continue;
}
// We only do testGetUserMedia(true) when privacy.resistFingerprinting
// is true, test if MediaStreamTrack.label is spoofed.
for (let track of stream.getTracks()) {
switch (track.kind) {
case "audio":
SimpleTest.is(track.label, "Internal Microphone", "AudioStreamTrack.label");
break;
case "video":
SimpleTest.is(track.label, "Internal Camera", "VideoStreamTrack.label");
break;
default:
SimpleTest.ok(false, "Unknown kind: " + track.kind);
break;
}
track.stop();
}
} catch (e) {
if (!expectDevices) {
SimpleTest.is(e.name, "NotAllowedError", message + " throws NotAllowedError");
} else {
SimpleTest.ok(false, message + " failed: " + e);
}
}
}
}
async function testDevices() {
await SpecialPowers.pushPrefEnv({
set: [
["privacy.resistFingerprinting", true],
["media.navigator.streams.fake", true]
]
});
await testEnumerateDevices(true); // should list a microphone and a camera
await testGetUserMedia(true); // should get audio and video streams
}
async function testNoDevices() {
await SpecialPowers.pushPrefEnv({
set: [
["privacy.resistFingerprinting", false],
["media.navigator.permission.device", false],
["media.navigator.streams.fake", false],
["media.audio_loopback_dev", "foo"],
["media.video_loopback_dev", "bar"]
]
});
await testEnumerateDevices(false); // should list nothing
await SpecialPowers.pushPrefEnv({
set: [
["privacy.resistFingerprinting", true]
]
});
await testEnumerateDevices(true); // should list a microphone and a camera
await testGetUserMedia(false); // should reject with NotAllowedError
}
createHTML({
title: "Neutralize the threat of fingerprinting of media devices API when 'privacy.resistFingerprinting' is true",
bug: "1372073"
});
runTest(async () => {
// Make sure enumerateDevices and getUserMedia work when
// privacy.resistFingerprinting is true.
await testDevices();
// Test that absence of devices can't be detected.
await testNoDevices();
});
</script>
</body>
</html>
|