tor-browser

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

test_getUserMedia_basicScreenshare.html (11383B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <head>
      4  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
      5  <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
      6 </head>
      7 <body>
      8 <pre id="test">
      9 <script type="application/javascript">
     10  createHTML({
     11    title: "getUserMedia Basic Screenshare Test",
     12    bug: "1211656",
     13    visible: true,
     14  });
     15 
     16  const {AppConstants} = SpecialPowers.ChromeUtils.importESModule(
     17    "resource://gre/modules/AppConstants.sys.mjs"
     18  );
     19 
     20  // Since the MacOS backend captures in the wrong rgb color profile we need
     21  // large thresholds there, and they vary greatly by color. We define
     22  // thresholds per platform and color here to still allow the test to run.
     23  // Since the colors used (red, green, blue, white) consist only of "pure"
     24  // components (0 or 255 for each component), the high thresholds on Mac will
     25  // still be able to catch an error where the image is corrupt, or if frames
     26  // don't flow.
     27  const thresholds = {
     28    // macos: captures in the display rgb color profile, which we treat as
     29    // sRGB, which is most likely wrong. These thresholds are needed currently
     30    // in CI. See bug 1827606.
     31    "macosx": { "red": 120, "green": 135, "blue": 35, "white": 10 },
     32    // windows: rounding errors in 1) conversion to I420 (the capture),
     33    // 2) downscaling, 3) conversion to RGB (for rendering).
     34    "win": { "red": 5, "green": 5, "blue": 10, "white": 5 },
     35    // linux: rounding errors in 1) conversion to I420 (the capture),
     36    // 2) downscaling, 3) conversion to RGB (for rendering).
     37    "linux": { "red": 5, "green": 5, "blue": 10, "white": 5 },
     38    // android: we don't have a screen capture backend.
     39    "android": { "red": 0, "green": 0, "blue": 0, "white": 0 },
     40    // other: here just because it's supported by AppConstants.platform.
     41    "other": { "red": 0, "green": 0, "blue": 0, "white": 0 },
     42  };
     43 
     44  const verifyScreenshare =
     45      async (video, helper, upleft, upright, downleft, downright) => {
     46    if (video.readyState < video.HAVE_CURRENT_DATA) {
     47      info("Waiting for data");
     48      await new Promise(r => video.onloadeddata = r);
     49    }
     50 
     51    // We assume video size will not change. Offsets help to account for a
     52    // square fullscreen-canvas, while the screen is rectangular.
     53    const offsetX = Math.max(0, video.videoWidth - video.videoHeight) / 2;
     54    const offsetY = Math.max(0, video.videoHeight - video.videoWidth) / 2;
     55 
     56    const verifyAround = async (internalX, internalY, color) => {
     57      // Pick a couple of samples around a coordinate to check for a color.
     58      // We check multiple rows and columns, to avoid most artifact issues.
     59      let areaSamples = [
     60        {dx: 0, dy: 0},
     61        {dx: 1, dy: 3},
     62        {dx: 8, dy: 5},
     63      ];
     64      const threshold = thresholds[AppConstants.platform][color.name];
     65      for (let {dx, dy} of areaSamples) {
     66        const x = offsetX + dx + internalX;
     67        const y = offsetY + dy + internalY;
     68        info(`Checking pixel (${[x,y]}) of total resolution `
     69          + `${video.videoWidth}x${video.videoHeight} against ${color.name}.`);
     70        let lastPixel = [-1, -1, -1, -1];
     71        await helper.waitForPixel(video, px => {
     72          lastPixel = Array.from(px);
     73          return helper.isPixel(px, color, threshold);
     74        }, {
     75          offsetX: x,
     76          offsetY: y,
     77          cancel: wait(30000).then(_ =>
     78            new Error(`Checking ${[x,y]} against ${color.name} timed out. ` +
     79                      `Got [${lastPixel}]. Threshold ${threshold}.`)),
     80        });
     81        ok(true, `Pixel (${[x,y]}) passed. Got [${lastPixel}].`);
     82      }
     83    };
     84 
     85    const screenSizeSq = Math.min(video.videoWidth, video.videoHeight);
     86 
     87    info("Waiting for upper left quadrant to become " + upleft.name);
     88    await verifyAround(screenSizeSq / 4, screenSizeSq / 4, upleft);
     89 
     90    info("Waiting for upper right quadrant to become " + upright.name);
     91    await verifyAround(screenSizeSq * 3 / 4, screenSizeSq / 4, upright);
     92 
     93    info("Waiting for lower left quadrant to become " + downleft.name);
     94    await verifyAround(screenSizeSq / 4, screenSizeSq * 3 / 4, downleft);
     95 
     96    info("Waiting for lower right quadrant to become " + downright.name);
     97    await verifyAround(screenSizeSq * 3 / 4, screenSizeSq * 3 / 4, downright);
     98  };
     99 
    100  /**
    101   * Run a test to verify that we can complete a start and stop media playback
    102   * cycle for a screenshare MediaStream on a video HTMLMediaElement.
    103   */
    104  runTest(async function () {
    105    await pushPrefs(
    106      ["full-screen-api.enabled", true],
    107      ["full-screen-api.allow-trusted-requests-only", false],
    108      ["full-screen-api.transition-duration.enter", "0 0"],
    109      ["full-screen-api.transition-duration.leave", "0 0"],
    110    );
    111 
    112    // Improve real estate for screenshots
    113    const test = document.getElementById("test");
    114    test.setAttribute("style", "height:0;margin:0;");
    115    const display = document.getElementById("display");
    116    display.setAttribute("style", "margin:0;");
    117    const testVideo = createMediaElement('video', 'testVideo');
    118    testVideo.removeAttribute("width");
    119    testVideo.removeAttribute("height");
    120    testVideo.setAttribute("style", "max-height:240px;");
    121 
    122    const canvas = document.createElement("canvas");
    123    canvas.width = canvas.height = 20;
    124    document.getElementById("content").appendChild(canvas);
    125    const draw = ([upleft, upright, downleft, downright]) => {
    126      helper.drawColor(canvas, helper[upleft], {offsetX: 0, offsetY: 0});
    127      helper.drawColor(canvas, helper[upright], {offsetX: 10, offsetY: 0});
    128      helper.drawColor(canvas, helper[downleft], {offsetX: 0, offsetY: 10});
    129      helper.drawColor(canvas, helper[downright], {offsetX: 10, offsetY: 10});
    130    };
    131    const helper = new CaptureStreamTestHelper2D(1, 1);
    132 
    133    const doVerify = async (stream, [upleft, upright, downleft, downright]) => {
    134      // Reset from potential earlier verification runs.
    135      testVideo.srcObject = null;
    136      const playback = new MediaStreamPlayback(testVideo, stream);
    137      playback.startMedia();
    138      await playback.verifyPlaying();
    139      const settings = stream.getTracks()[0].getSettings();
    140      is(settings.width, testVideo.videoWidth,
    141         "Width setting should match video width");
    142      is(settings.height, testVideo.videoHeight,
    143         "Height setting should match video height");
    144      await SpecialPowers.wrap(canvas).requestFullscreen();
    145      try {
    146        await verifyScreenshare(testVideo, helper, helper[upleft], helper[upright],
    147                                helper[downleft], helper[downright]);
    148      } finally {
    149        await playback.stopTracksForStreamInMediaPlayback();
    150        await SpecialPowers.wrap(document).exitFullscreen();
    151        // We wait a bit extra here to make sure we have completely left
    152        // fullscreen when the --screenshot-on-fail screenshot is captured.
    153        await wait(300);
    154      }
    155    };
    156 
    157    info("Testing screenshare without constraints");
    158    SpecialPowers.wrap(document).notifyUserGestureActivation();
    159    let stream = await getUserMedia({video: {mediaSource: "screen"}});
    160    let settings = stream.getTracks()[0].getSettings();
    161    ok(settings.width > 0 && settings.width <= 8192,
    162       `Width setting ${settings.width} should be set after gUM`);
    163    ok(settings.height > 0 && settings.height <= 8192,
    164       `Height setting ${settings.height} should be set after gUM`);
    165    let colors = ["red", "blue", "green", "white"];
    166    draw(colors);
    167    await doVerify(stream, colors);
    168    const screenWidth = testVideo.videoWidth;
    169    const screenHeight = testVideo.videoHeight;
    170 
    171    info("Testing screenshare with size and framerate constraints");
    172    SpecialPowers.wrap(document).notifyUserGestureActivation();
    173    for (const track of stream.getTracks()) {
    174      track.stop();
    175    }
    176    stream = await getUserMedia({
    177      video: {
    178        mediaSource: 'screen',
    179        width: {
    180          min: '10',
    181          max: '100'
    182        },
    183        height: {
    184          min: '10',
    185          max: '100'
    186        },
    187        frameRate: {
    188          min: '10',
    189          max: '15'
    190        },
    191      },
    192    });
    193    settings = stream.getTracks()[0].getSettings();
    194    ok(settings.width >= 10 && settings.width <= 100,
    195       `Width setting ${settings.width} should be correct after gUM`);
    196    ok(settings.height >= 10 && settings.height <= 100,
    197       `Height setting ${settings.height} should be correct after gUM`);
    198    colors = ["green", "red", "white", "blue"];
    199    draw(colors);
    200    const streamClone = stream.clone();
    201    await doVerify(streamClone, colors);
    202    settings = stream.getTracks()[0].getSettings();
    203    ok(settings.width >= 10 && settings.width <= 100,
    204       `Width setting ${settings.width} should be within constraints`);
    205    ok(settings.height >= 10 && settings.height <= 100,
    206       `Height setting ${settings.height} should be within constraints`);
    207    is(settings.width, testVideo.videoWidth,
    208       "Width setting should match video width");
    209    is(settings.height, testVideo.videoHeight,
    210       "Height setting should match video height");
    211    let expectedHeight = (screenHeight * settings.width) / screenWidth;
    212    ok(Math.abs(expectedHeight - settings.height) <= 1,
    213       "Aspect ratio after constrained gUM should be close enough");
    214 
    215    info("Testing modifying screenshare with applyConstraints");
    216    testVideo.srcObject = stream;
    217    testVideo.play();
    218    await new Promise(r => testVideo.onloadeddata = r);
    219    const resize = haveEvent(
    220      testVideo, "resize", wait(5000, new Error("Timeout waiting for resize")));
    221    await stream.getVideoTracks()[0].applyConstraints({
    222      mediaSource: 'screen',
    223      width: 200,
    224      height: 200,
    225      frameRate: {
    226        min: '5',
    227        max: '10'
    228      }
    229    });
    230    // getSettings() should report correct size as soon as applyConstraints()
    231    // resolves - bug 1453259. Until fixed, check that we at least report
    232    // something sane.
    233    const newSettings = stream.getTracks()[0].getSettings();
    234    ok(newSettings.width > settings.width && newSettings.width < screenWidth,
    235       `Width setting ${newSettings.width} should have increased after applyConstraints`);
    236    ok(newSettings.height > settings.height && newSettings.height < screenHeight,
    237       `Height setting ${newSettings.height} should have increased after applyConstraints`);
    238    await resize;
    239    settings = stream.getTracks()[0].getSettings();
    240    ok(settings.width > 100 && settings.width < screenWidth,
    241       `Width setting ${settings.width} should have increased after first frame after applyConstraints`);
    242    ok(settings.height > 100 && settings.height < screenHeight,
    243       `Height setting ${settings.height} should have increased after first frame after applyConstraints`);
    244    is(settings.width, testVideo.videoWidth,
    245       "Width setting should match video width");
    246    is(settings.height, testVideo.videoHeight,
    247       "Height setting should match video height");
    248    expectedHeight = (screenHeight * settings.width) / screenWidth;
    249    ok(Math.abs(expectedHeight - settings.height) <= 1,
    250       "Aspect ratio after applying constraints should be close enough");
    251    colors = ["white", "green", "blue", "red"];
    252    draw(colors);
    253    await doVerify(stream, colors);
    254    for (const track of [...stream.getTracks(), ...streamClone.getTracks()]) {
    255      track.stop();
    256    }
    257  });
    258 </script>
    259 </pre>
    260 </body>
    261 </html>