tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_ondevicechange.html (6779B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <head>
      4  <meta charset="utf-8">
      5  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
      6 </head>
      7 <body>
      8 <script type="application/javascript">
      9 "use strict";
     10 
     11 createHTML({
     12  title: "ondevicechange tests",
     13  bug: "1152383"
     14 });
     15 
     16 async function resolveOnEvent(target, name) {
     17  return new Promise(r => target.addEventListener(name, r, {once: true}));
     18 }
     19 let eventCount = 0;
     20 async function triggerVideoDevicechange() {
     21  ++eventCount;
     22  // "media.getusermedia.fake-camera-name" specifies the name of the single
     23  // fake video camera.
     24  // Changing the pref imitates replacing one device with another.
     25  return pushPrefs(["media.getusermedia.fake-camera-name",
     26                    `devicechange ${eventCount}`])
     27 }
     28 function addIframe() {
     29  const iframe = document.createElement("iframe");
     30  // Workaround for bug 1743933
     31  iframe.loadPromise = resolveOnEvent(iframe, "load");
     32  document.documentElement.appendChild(iframe);
     33  return iframe;
     34 }
     35 
     36 runTest(async () => {
     37  // A toplevel Window and an iframe Windows are compared for devicechange
     38  // events.
     39  const iframe1 = addIframe();
     40  const iframe2 = addIframe();
     41  await Promise.all([
     42    iframe1.loadPromise,
     43    iframe2.loadPromise,
     44    pushPrefs(
     45      // Use the fake video backend to trigger devicechange events.
     46      ["media.navigator.streams.fake", true],
     47      // Loopback would override fake.
     48      ["media.video_loopback_dev", ""],
     49      // Make fake devices count as real, permission-wise, or devicechange
     50      // events won't be exposed
     51      ["media.navigator.permission.fake", true],
     52      // For gUM.
     53      ["media.navigator.permission.disabled", true]
     54    ),
     55  ]);
     56  const topDevices = navigator.mediaDevices;
     57  const frame1Devices = iframe1.contentWindow.navigator.mediaDevices;
     58  const frame2Devices = iframe2.contentWindow.navigator.mediaDevices;
     59  // Initialization of MediaDevices::mLastPhysicalDevices is triggered when
     60  // ondevicechange is set but tests "media.getusermedia.fake-camera-name"
     61  // asynchronously.  Wait for getUserMedia() completion to ensure that the
     62  // pref has been read before doDevicechanges() changes it.
     63  frame1Devices.ondevicechange = () => {};
     64  const topEventPromise = resolveOnEvent(topDevices, "devicechange");
     65  const frame2EventPromise = resolveOnEvent(frame2Devices, "devicechange");
     66  (await frame1Devices.getUserMedia({video: true})).getTracks()[0].stop();
     67 
     68  await Promise.all([
     69    resolveOnEvent(frame1Devices, "devicechange"),
     70    triggerVideoDevicechange(),
     71  ]);
     72  ok(true,
     73     "devicechange event is fired when gUM has been in use");
     74  // The number of devices has not changed.  Race a settled Promise to check
     75  // that no devicechange event has been received in frame2.
     76  const racer = {};
     77  is(await Promise.race([frame2EventPromise, racer]), racer,
     78     "devicechange event is NOT fired in iframe2 for replaced device when " +
     79     "gUM has NOT been in use");
     80  // getUserMedia() is invoked on frame2Devices after a first device list
     81  // change but before returning to the previous state, in order to test that
     82  // the device set is compared with the set after previous device list
     83  // changes regardless of whether a "devicechange" event was previously
     84  // dispatched.
     85  (await frame2Devices.getUserMedia({video: true})).getTracks()[0].stop();
     86  // Revert device list change.
     87  await Promise.all([
     88    resolveOnEvent(frame1Devices, "devicechange"),
     89    resolveOnEvent(frame2Devices, "devicechange"),
     90    SpecialPowers.popPrefEnv(),
     91  ]);
     92  ok(true,
     93     "devicechange event is fired on return to previous list " +
     94     "after gUM has been is use");
     95 
     96  const frame1EventPromise1 = resolveOnEvent(frame1Devices, "devicechange");
     97  while (true) {
     98    const racePromise = Promise.race([
     99      frame1EventPromise1,
    100      // 100ms is half the coalescing time in MediaManager::DeviceListChanged().
    101      wait(100, {type: "wait done"}),
    102    ]);
    103    await triggerVideoDevicechange();
    104    if ((await racePromise).type == "devicechange") {
    105      ok(true,
    106         "devicechange event is fired even when hardware changes continue");
    107      break;
    108    }
    109  }
    110 
    111  is(await Promise.race([topEventPromise, racer]), racer,
    112     "devicechange event is NOT fired for device replacements when " +
    113     "gUM has NOT been in use");
    114 
    115  if (navigator.userAgent.includes("Android")) {
    116    todo(false, "test assumes Firefox-for-Desktop specific API and behavior");
    117    return;
    118  }
    119  // Open a new tab, which is expected to receive focus and hide the first tab.
    120  const tab = window.open();
    121  SimpleTest.registerCleanupFunction(() => tab.close());
    122  await Promise.all([
    123    resolveOnEvent(document, 'visibilitychange'),
    124    resolveOnEvent(tab, 'focus'),
    125  ]);
    126  ok(tab.document.hasFocus(), "tab.document.hasFocus()");
    127  await Promise.all([
    128    resolveOnEvent(tab, 'blur'),
    129    SpecialPowers.spawnChrome([], function focusUrlBar() {
    130      this.browsingContext.topChromeWindow.gURLBar.focus();
    131    }),
    132  ]);
    133  ok(!tab.document.hasFocus(), "!tab.document.hasFocus()");
    134  is(document.visibilityState, 'hidden', 'visibilityState')
    135  const frame1EventPromise2 = resolveOnEvent(frame1Devices, "devicechange");
    136  const tabDevices = tab.navigator.mediaDevices;
    137  tabDevices.ondevicechange = () => {};
    138  const tabStream = await tabDevices.getUserMedia({video: true});
    139  // Trigger and await two devicechanges on tabDevices to wait long enough to
    140  // provide that a devicechange on another MediaDevices would be received.
    141  for (let i = 0; i < 2; ++i) {
    142    await Promise.all([
    143      resolveOnEvent(tabDevices, "devicechange"),
    144      triggerVideoDevicechange(),
    145    ]);
    146  };
    147  is(await Promise.race([frame1EventPromise2, racer]), racer,
    148     "devicechange event is NOT fired while tab is in background");
    149  tab.close();
    150  await resolveOnEvent(document, 'visibilitychange');
    151  is(document.visibilityState, 'visible', 'visibilityState')
    152  await frame1EventPromise2;
    153  ok(true, "devicechange event IS fired when tab returns to foreground");
    154 
    155  const audioLoopbackDev =
    156        SpecialPowers.getCharPref("media.audio_loopback_dev", "");
    157  if (!navigator.userAgent.includes("Linux")) {
    158    todo_isnot(audioLoopbackDev, "", "audio_loopback_dev");
    159    return;
    160  }
    161  isnot(audioLoopbackDev, "", "audio_loopback_dev");
    162  await Promise.all([
    163    resolveOnEvent(topDevices, "devicechange"),
    164    pushPrefs(["media.audio_loopback_dev", "none"]),
    165  ]);
    166  ok(true,
    167     "devicechange event IS fired when last audio device is removed and " +
    168     "gUM has NOT been in use");
    169  await Promise.all([
    170    resolveOnEvent(topDevices, "devicechange"),
    171    pushPrefs(["media.audio_loopback_dev", audioLoopbackDev]),
    172  ]);
    173  ok(true,
    174     "devicechange event IS fired when first audio device is added and " +
    175     "gUM has NOT been in use");
    176 });
    177 
    178 </script>
    179 </body>
    180 </html>