DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (b6d82b1a6b02)

VCS Links

Line Code
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>