tor-browser

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

MediaStreamTrack-resizeMode.https.html (16226B)


      1 <!doctype html>
      2 <title>MediaStreamTrack video resizeMode. Assumes Mozilla's fake camera source with 480p and 720p capabilities.</title>
      3 <meta name="timeout" content="long">
      4 <p class="instructions">When prompted, accept to share your video stream.</p>
      5 <script src=/resources/testharness.js></script>
      6 <script src=/resources/testharnessreport.js></script>
      7 <script src=/resources/testdriver.js></script>
      8 <script src=/resources/testdriver-vendor.js></script>
      9 <script src=settings-helper.js></script>
     10 <script src=video-test-helper.js></script>
     11 <script>
     12  "use strict"
     13 
     14  // Native capabilities supported by the fake camera.
     15  const nativeLow = {width: 640, height: 480, frameRate: 30, resizeMode: "none"};
     16  const nativeHigh = {width: 1280, height: 720, frameRate: 10, resizeMode: "none"};
     17 
     18 
     19  [
     20    [{resizeMode: "none", width: 500}, nativeLow],
     21    [{resizeMode: "none", height: 500}, nativeLow],
     22    [{resizeMode: "none", width: 500, height: 500}, nativeLow],
     23    [{resizeMode: "none", frameRate: 50}, nativeLow],
     24    [{resizeMode: "none", width: 500, height: 500, frameRate: 50}, nativeLow],
     25    [{resizeMode: "none", width: 1000}, nativeHigh],
     26    [{resizeMode: "none", height: 1000}, nativeHigh],
     27    [{resizeMode: "none", width: 1000, height: 1000}, nativeHigh],
     28    [{resizeMode: "none", frameRate: 1}, nativeHigh, [3, 12]],
     29    [{resizeMode: "none", width: 1000, height: 1000, frameRate: 1}, nativeHigh],
     30    [
     31      {resizeMode: "crop-and-scale"},
     32      {resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
     33    ],
     34    [
     35      {resizeMode: "crop-and-scale", height: 500},
     36      {resizeMode: "crop-and-scale", width: 889, height: 500, frameRate: 10}
     37    ],
     38    [
     39      {resizeMode: "crop-and-scale", width: {min: 500}, height: {max: 200}},
     40      {resizeMode: "crop-and-scale", width: 500, height: 200, frameRate: 30}
     41    ],
     42    [
     43      {resizeMode: "crop-and-scale", frameRate: 50},
     44      {resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
     45    ],
     46    [
     47      {resizeMode: "crop-and-scale", width: 10000, frameRate: {min: 30}},
     48      {resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
     49    ],
     50    [
     51      {resizeMode: "crop-and-scale", frameRate: {exact: 5}},
     52      {resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 5},
     53      [2, 7]
     54    ],
     55  ].forEach(([video, expected, testFramerate]) => promise_test(async t => {
     56      const stream = await navigator.mediaDevices.getUserMedia({video});
     57      const [track] = stream.getTracks();
     58      t.add_cleanup(() => track.stop());
     59      const settings = track.getSettings();
     60      for (const key of Object.keys(expected)) {
     61        assert_equals(settings[key], expected[key], key);
     62      }
     63      if (testFramerate) {
     64        const [low, high] = testFramerate;
     65        await test_framerate_between_exclusive(t, track, low, high);
     66      }
     67    }, `gUM gets ${JSON.stringify(expected)} mode by ${JSON.stringify(video)}`));
     68 
     69  promise_test(async t => {
     70    const stream = await navigator.mediaDevices.getUserMedia({video: {resizeMode: "none", height: 720}});
     71    const [track] = stream.getTracks();
     72    const stream2 = await navigator.mediaDevices.getUserMedia({video: {resizeMode: "crop-and-scale", height: 300}});
     73    const [track2] = stream2.getTracks();
     74    t.add_cleanup(() => {
     75      track.stop();
     76      track2.stop();
     77    });
     78    const settings = track.getSettings();
     79    assert_equals(settings.resizeMode, "none", "track resizeMode");
     80    assert_equals(settings.width, 1280, "track width");
     81    assert_equals(settings.height, 720, "track height");
     82    assert_equals(settings.frameRate, 10, "track framerate");
     83    const settings2 = track2.getSettings();
     84    assert_equals(settings2.resizeMode, "crop-and-scale", "track2 resizeMode");
     85    assert_equals(settings2.width, 400, "track2 width");
     86    assert_equals(settings2.height, 300, "track2 height");
     87    // Bug 1988466: source is configured at 10fps so we cannot completely
     88    // masquerade the selected capability.
     89    assert_equals(settings2.frameRate, 30, "track2 framerate");
     90  }, `gUM gets expected downscaling with competing capabilities`);
     91 
     92  promise_test(async t => {
     93    try {
     94      const stream = await navigator.mediaDevices.getUserMedia(
     95          {video: {resizeMode: "none", width: {min: 2000}}}
     96      );
     97      const [track] = stream.getTracks();
     98      t.add_cleanup(() => track.stop());
     99    } catch(e) {
    100      assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
    101      return;
    102    }
    103    assert_unreached("gUM is rejected with impossible width");
    104  }, "gUM is rejected by resizeMode none and impossible min-width");
    105 
    106  promise_test(async t => {
    107    try {
    108      const stream = await navigator.mediaDevices.getUserMedia(
    109          {video: {resizeMode: "none", width: {max: 200}}}
    110      );
    111      const [track] = stream.getTracks();
    112      t.add_cleanup(() => track.stop());
    113    } catch(e) {
    114      assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
    115      return;
    116    }
    117    assert_unreached("gUM is rejected with impossible width");
    118  }, "gUM is rejected by resizeMode none and impossible max-width");
    119 
    120  promise_test(async t => {
    121    try {
    122      const stream = await navigator.mediaDevices.getUserMedia(
    123          {video: {resizeMode: "crop-and-scale", width: {min: 2000}}}
    124      );
    125      const [track] = stream.getTracks();
    126      t.add_cleanup(() => track.stop());
    127    } catch(e) {
    128      assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
    129      return;
    130    }
    131    assert_unreached("gUM is rejected with impossible width");
    132  }, "gUM is rejected by resizeMode crop-and-scale and impossible width");
    133 
    134  promise_test(async t => {
    135    try {
    136      const stream = await navigator.mediaDevices.getUserMedia(
    137          {video: {resizeMode: "crop-and-scale", frameRate: {min: 50}}}
    138      );
    139      const [track] = stream.getTracks();
    140      t.add_cleanup(() => track.stop());
    141    } catch(e) {
    142      assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
    143      return;
    144    }
    145    assert_unreached("gUM is rejected with impossible fps");
    146  }, "gUM is rejected by resizeMode crop-and-scale and impossible fps");
    147 
    148 
    149  [
    150    [{resizeMode: "none", width: 500}, nativeLow],
    151    [{resizeMode: "none", height: 500}, nativeLow],
    152    [{resizeMode: "none", width: 500, height: 500}, nativeLow],
    153    [{resizeMode: "none", frameRate: 50}, nativeLow],
    154    [{resizeMode: "none", width: 500, height: 500, frameRate: 50}, nativeLow],
    155    [{resizeMode: "none", width: 1000}, nativeHigh],
    156    [{resizeMode: "none", height: 1000}, nativeHigh],
    157    [{resizeMode: "none", width: 1000, height: 1000}, nativeHigh],
    158    [{resizeMode: "none", frameRate: 1}, nativeHigh, [3, 12]],
    159    [{resizeMode: "none", width: 1000, height: 1000, frameRate: 1}, nativeHigh],
    160    [
    161      {resizeMode: "crop-and-scale"},
    162      {resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
    163    ],
    164    [
    165      {resizeMode: "crop-and-scale", height: 400},
    166      {resizeMode: "crop-and-scale", width: 533, height: 400, frameRate: 30}
    167    ],
    168    [
    169      {resizeMode: "crop-and-scale", height: 500},
    170      {resizeMode: "crop-and-scale", width: 889, height: 500, frameRate: 10}
    171    ],
    172    [
    173      {resizeMode: "crop-and-scale", height: {exact: 500}},
    174      {resizeMode: "crop-and-scale", width: 889, height: 500, frameRate: 10}
    175    ],
    176    [
    177      {resizeMode: "crop-and-scale", width: {min: 500}, height: {max: 200}},
    178      {resizeMode: "crop-and-scale", width: 500, height: 200, frameRate: 30}
    179    ],
    180    [
    181      {resizeMode: "crop-and-scale", frameRate: 50},
    182      {resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
    183    ],
    184    [
    185      {resizeMode: "crop-and-scale", width: 10000, frameRate: {min: 30}},
    186      {resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 30}
    187    ],
    188    [
    189      {resizeMode: "crop-and-scale", frameRate: {exact: 5}},
    190      {resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 5},
    191      [2, 7]
    192    ],
    193  ].forEach(([video, expected, testFramerate]) => promise_test(async t => {
    194      const stream = await navigator.mediaDevices.getUserMedia({video: true});
    195      const [track] = stream.getTracks();
    196      t.add_cleanup(() => track.stop());
    197      await track.applyConstraints(video);
    198      const settings = track.getSettings();
    199      for (const key of Object.keys(expected)) {
    200        assert_equals(settings[key], expected[key], key);
    201      }
    202      if (testFramerate) {
    203        const [low, high] = testFramerate;
    204        await test_framerate_between_exclusive(t, track, low, high);
    205      }
    206    }, `applyConstraints gets ${JSON.stringify(expected)} mode by ${JSON.stringify(video)}`));
    207 
    208  promise_test(async t => {
    209    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    210    const [track] = stream.getTracks();
    211    t.add_cleanup(() => track.stop());
    212    try {
    213      await track.applyConstraints({resizeMode: "none", width: {min: 2000}})
    214    } catch(e) {
    215      assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
    216      return;
    217    }
    218    assert_unreached("applyConstraints is rejected with impossible width");
    219  }, "applyConstraints is rejected by resizeMode none and impossible min-width");
    220 
    221  promise_test(async t => {
    222    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    223    const [track] = stream.getTracks();
    224    t.add_cleanup(() => track.stop());
    225    try {
    226      await track.applyConstraints({resizeMode: "none", width: {max: 200}})
    227    } catch(e) {
    228      assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
    229      return;
    230    }
    231    assert_unreached("applyConstraints is rejected with impossible width");
    232  }, "applyConstraints is rejected by resizeMode none and impossible max-width");
    233 
    234  promise_test(async t => {
    235    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    236    const [track] = stream.getTracks();
    237    t.add_cleanup(() => track.stop());
    238    try {
    239      await track.applyConstraints({resizeMode: "crop-and-scale", width: {min: 2000}})
    240    } catch(e) {
    241      assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
    242      return;
    243    }
    244    assert_unreached("applyConstraints is rejected with impossible width");
    245  }, "applyConstraints is rejected by resizeMode crop-and-scale and impossible width");
    246 
    247  promise_test(async t => {
    248    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    249    const [track] = stream.getTracks();
    250    t.add_cleanup(() => track.stop());
    251    try {
    252      await track.applyConstraints({resizeMode: "crop-and-scale", frameRate: {min: 50}});
    253    } catch(e) {
    254      assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`);
    255      return;
    256    }
    257    assert_unreached("applyConstraints is rejected with impossible fps");
    258  }, "applyConstraints is rejected by resizeMode crop-and-scale impossible fps");
    259 
    260 
    261  // Note these gDM tests will fail if our own window is on a screen different
    262  // than the system's first screen. They're functions in case the browser
    263  // window needs to be moved to the first screen during the test in order to
    264  // pass.
    265  function screenPixelRatio() { return SpecialPowers.wrap(window).desktopToDeviceScale; }
    266  function screenWidth() { return window.screen.width * window.devicePixelRatio; }
    267  function screenHeight() { return window.screen.height * window.devicePixelRatio; }
    268  function desktopWidth() {
    269    // TODO: Bug 1965499 - scale down by screenPixelRatio by default in resizeMode: crop-and-scale.
    270    // return screenWidth() / screenPixelRatio();
    271    return screenWidth();
    272  }
    273  function desktopHeight() {
    274    // TODO: Bug 1965499 - scale down by screenPixelRatio by default in resizeMode: crop-and-scale.
    275    // return screenHeight() / screenPixelRatio();
    276    return screenHeight();
    277  }
    278 
    279  promise_test(async t => {
    280    await test_driver.bless('getDisplayMedia()');
    281    const stream = await navigator.mediaDevices.getDisplayMedia(
    282        {video: {resizeMode: "none", width: 100}}
    283    );
    284    const [track] = stream.getTracks();
    285    t.add_cleanup(() => track.stop());
    286    assert_equals(track.getSettings().width, screenWidth(), "width");
    287    assert_equals(track.getSettings().height, screenHeight(), "height");
    288    assert_equals(track.getSettings().frameRate, 60, "framerate");
    289    assert_equals(track.getSettings().resizeMode, "none", "resizeMode");
    290  }, "gDM gets full screen resolution by width");
    291 
    292  // TODO: By default we shouldn't be multiplying with window.devicePixelRatio (bug 1703991).
    293  function defaultScreen() {
    294    return {
    295      resizeMode: "crop-and-scale",
    296      width: screenWidth(),
    297      height: screenHeight(),
    298      frameRate: 30,
    299    };
    300  }
    301  // TODO: Should get the source's real refresh rate for frameRate (bug 1984363).
    302  function nativeScreen() {
    303    return {
    304      resizeMode: "none",
    305      width: screenWidth(),
    306      height: screenHeight(),
    307      frameRate: 60
    308    };
    309  }
    310 
    311  [
    312    [{resizeMode: "none", width: 100}, nativeScreen],
    313    [{resizeMode: "none", frameRate: 50}, nativeScreen],
    314    [{resizeMode: "crop-and-scale"}, defaultScreen],
    315    [{resizeMode: "crop-and-scale", height: 100}, () => ({
    316        resizeMode: "crop-and-scale",
    317        width: Math.round(screenWidth() / screenHeight() * 100),
    318        height: 100,
    319        frameRate: 30
    320      })],
    321    [{resizeMode: "crop-and-scale", frameRate: 5}, () => {
    322        const { width, height } = defaultScreen();
    323        return { width, height, frameRate: 5};
    324      }, [2, 7]],
    325    [{resizeMode: "crop-and-scale", frameRate: 50}, () => {
    326        const { width, height } = defaultScreen();
    327        return { width, height, frameRate: 50};
    328      }],
    329    [{resizeMode: "crop-and-scale", frameRate: 5000}, () => {
    330        const { width, height } = defaultScreen();
    331        return { width, height, frameRate: 120};
    332      }],
    333  ].forEach(([video, expectedFunc, testFramerate]) => {
    334      let expected;
    335      promise_test(async t => {
    336          expected = expectedFunc();
    337          await test_driver.bless('getDisplayMedia()');
    338          const stream = await navigator.mediaDevices.getDisplayMedia({video});
    339          const [track] = stream.getTracks();
    340          t.add_cleanup(() => track.stop());
    341          const settings = track.getSettings();
    342          for (const key of Object.keys(expected)) {
    343            assert_equals(settings[key], expected[key], key);
    344          }
    345          if (testFramerate) {
    346            const [low, high] = testFramerate;
    347            await test_framerate_between_exclusive(t, track, low, high);
    348          }
    349        }, `gDM gets expected mode by ${JSON.stringify(video)}`);
    350    });
    351 
    352  promise_test(async t => {
    353    await test_driver.bless('getDisplayMedia()');
    354    const stream = await navigator.mediaDevices.getDisplayMedia(
    355        {video: {
    356            resizeMode: "crop-and-scale",
    357            width: 400,
    358            height: 400
    359        }}
    360    );
    361    const [track] = stream.getTracks();
    362    t.add_cleanup(() => track.stop());
    363    const expected = findFittestResolutionSetting(
    364      screenWidth(),
    365      screenHeight(),
    366      track.getConstraints()
    367    );
    368    assert_equals(track.getSettings().width, expected.width, "width");
    369    assert_equals(track.getSettings().height, expected.height, "height");
    370    assert_approx_equals(
    371      track.getSettings().width / track.getSettings().height,
    372      desktopWidth() / desktopHeight(),
    373      0.01,
    374      "aspect ratio"
    375    );
    376    assert_equals(track.getSettings().resizeMode, "crop-and-scale", "resizeMode");
    377  }, "gDM doesn't crop with only ideal dimensions");
    378 
    379  promise_test(async t => {
    380    await test_driver.bless('getDisplayMedia()');
    381    const stream = await navigator.mediaDevices.getDisplayMedia(
    382        {video: {
    383            resizeMode: "crop-and-scale",
    384            width: {max: 400},
    385            height: {ideal: 400}
    386        }}
    387    );
    388    const [track] = stream.getTracks();
    389    t.add_cleanup(() => track.stop());
    390    assert_equals(track.getSettings().width, 400, "width");
    391    assert_equals(
    392      track.getSettings().height,
    393      Math.round(screenHeight() / screenWidth() * 400),
    394      "height"
    395    );
    396    assert_equals(track.getSettings().resizeMode, "crop-and-scale", "resizeMode");
    397  }, "gDM doesn't crop with ideal and max dimensions");
    398 </script>