summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/mediacapture-streams/BrowserCaptureMediaStreamTrack-restrictTo.https.html
blob: 4b0da740bdf72a94b639c60f3732ce7b51e28792 (plain)
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
<!doctype html>
<html>

<head>
  <title>BrowserCaptureMediaStreamTrack restrictTo()</title>
  <link rel="help" href="https://screen-share.github.io/element-capture/">
</head>

<body>
  <p class="instructions">
    When prompted, accept to give permission to use your audio, video devices.
  </p>
  <h1 class="instructions">Description</h1>
  <p class="instructions">
    This test checks that restricting BrowserCaptureMediaStreamTrack works as
    expected.
  </p>

  <style>
    div {
      height: 100px;
    }
    .stacking {
      opacity: 0.9;
    }
    #container {
      columns:4;
      column-fill:auto;
    }
    .fragmentize {
      height: 50px;
    }
    #target {
      background: linear-gradient(red, blue);
    }
  </style>


  <div id='container'>
    <div id='target'></div>
  </div>
  <video id="video"
         style="border: 2px blue dotted; width: 250px; height: 250px;"
         autoplay playsinline muted></video>

  <script src=/resources/testharness.js></script>
  <script src=/resources/testharnessreport.js></script>
  <script src=/resources/testdriver.js></script>
  <script src=/resources/testdriver-vendor.js></script>

  <script>
    "use strict";

    // For more information, see:
    // https://screen-share.github.io/element-capture/#elements-eligible-for-restriction
    const EligibilityRequirement = {
      StackingContext: "StackingContext",
      OnlyOneBoxFragment: "OnlyOneBoxFragment",
      FlattenedIn3D: "FlattenedIn3D",
    };

    // The target div.
    const div = document.getElementById('target');

    // Returns a promise that, if successful, will resolve to a media stream.
    async function getDisplayMedia() {
      return test_driver.bless('getDisplayMedia', () =>
        navigator.mediaDevices.getDisplayMedia({
          video: { displaySurface: "browser" },
          selfBrowserSurface: "include",
        }));
    }

    // Returns a promise that will resolve successfully if at least one frame is
    // read before the timeout.
    function assertFrameRead(t, state, message) {
      const last_frame_count = state.frame_count;
      return t.step_wait(() => state.frame_count > last_frame_count,
        message, 5000, 10);
    }

    // Returns a promise that will resolve successfully if there are no frames
    // produced for an entire second after being called.
    function assertStopsProducingFrames(t, state, message) {
      let last_frame_count = state.frame_count;

      return t.step_timeout(() => {
        assert_equals(state.frame_count, last_frame_count);
      }, 1000);
    }

    function makeDivEligible() {
      // Must always have a stacking context to be eligible.
      div.classList.add("stacking");
      div.parentElement.classList.remove("fragmented");
      div.style.transform = "";
    }

    function makeDivIneligible(state) {
      switch(state.eligibilityParam) {
        case EligibilityRequirement.StackingContext:
          div.classList.remove("stacking");
          break;

        case EligibilityRequirement.OnlyOneBoxFragment:
          div.parentElement.classList.add("fragmented");
          break;

        case EligibilityRequirement.FlattenedIn3D:
          div.style.transform = "rotateY(90deg)";
          break;
      }
    }

    // Restore element state after each test.
    function cleanupDiv() {
      div.classList.remove("stacking");
      div.parentElement.classList.remove("fragmented");
      div.style.transform = "";
    }

    function startAnimation(t, state) {
      let count = 0;
      function animate() {
        if (!state.running) {
          return;
        }
        count += 1;
        div.innerText = count;
        window.requestAnimationFrame(animate);
      }
      window.requestAnimationFrame(animate);

      // Stop animation as part of cleanup.
      t.add_cleanup(() => { state.running = false; });
    }

    // Updates the state.frame_count value whenever a new frame is received on
    // the passed in media stream track.
    async function readFromTrack(state, track) {
      while (state.running) {
        const reader = new MediaStreamTrackProcessor(track).readable.getReader();
        while (true) {
          const frameOrDone = await reader.read();
          if (frameOrDone.done) {
            break;
          }
          frameOrDone.value.close();
          state.frame_count += 1;
        }
      }
    }

    // Parameterized test method. Note that this returns a Promise that will be
    // resolved to represent success of the entire promise test.
    async function runTest(t, eligibilityParam) {
      let state = {
        eligibilityParam: eligibilityParam,
        frame_count: 0,
        running: true,
        reading: false,
      };
      startAnimation(t, state);

      let videoTrack = undefined;
      return getDisplayMedia().then(stream => {
        t.add_cleanup(() => {
          stream.getTracks().forEach(track => track.stop());
        });
        assert_true(!!stream, "should have resolved to a stream.");
        assert_true(stream.active, "stream should be active.");
        assert_equals(stream.getVideoTracks().length, 1);

        [videoTrack] = stream.getVideoTracks();
        assert_true(videoTrack instanceof MediaStreamTrack,
          "track should be either MediaStreamTrack or a subclass thereof.");
        assert_equals(videoTrack.readyState, "live", "track should be live.");

        // Consume the stream in a video element.
        const video = document.querySelector('video');
        video.srcObject = stream;

        // Remove the video source, so that the stream object can be released.
        t.add_cleanup(() => {video.srcObject = null});

        // Keep track of the number of frames used.
        const readPromise = readFromTrack(state, videoTrack);
        t.add_cleanup(() => readPromise);

        return assertFrameRead(t, state, "Track should produce frames.");
      }).then(() => {
        assert_true(!!RestrictionTarget, "RestrictionTarget exposed.");
        assert_true(!!RestrictionTarget.fromElement,
          "RestrictionTarget.fromElement exposed.");

        return RestrictionTarget.fromElement(div);
      }).then(restrictionTarget => {
        assert_true(!!videoTrack.restrictTo, "restrictTo exposed.");
        assert_true(typeof videoTrack.restrictTo === 'function',
          "restrictTo is a function.");

        return videoTrack.restrictTo(restrictionTarget);
      }).then(() => {
        // By default, elements are not eligible for restriction due to not being
        // placed in their own stacking context.
        return assertStopsProducingFrames(t, state,
          "No new frames after restriction.");
      });

    // TODO(crbug.com/333770107): once the issue with the
    // --disable-threaded-compositing flag is resolved on Chrome's check in bots
    // the rest of this test should be enabled.
    //   ).then(() => {
    //     // Should be unpaused now that the element is eligible.
    //     makeDivEligible();

    //     // Make sure the element state is restored to default between tests.
    //     t.add_cleanup(() => { cleanupDiv(); });

    //     // Restart the reader now that the stream is producing frames again.
    //     return assertFrameRead(t, state,
    //       "Received at least one frame after becoming eligible.");
    //   }).then(() => {

    //     // Should pause if it becomes ineligible again.
    //     makeDivIneligible(state);
    //     return assertStopsProducingFrames(t, state,
    //       "No new frames after becoming ineligible again.");
    //   });
    }

    // Test parameterizations.
    [
      EligibilityRequirement.StackingContext,
      EligibilityRequirement.OnlyOneBoxFragment,
      EligibilityRequirement.FlattenedIn3D,
    ].forEach(param =>
      promise_test(t => runTest(t, param),
        `Tests that restricting MediaStreamTrack objects works as expected (${param}).`
    ));

  </script>
</body>

</html>