| <!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> |