BrowserCaptureMediaStreamTrack-restrictTo.https.html (7947B)
1 <!doctype html> 2 <html> 3 4 <head> 5 <title>BrowserCaptureMediaStreamTrack restrictTo()</title> 6 <link rel="help" href="https://screen-share.github.io/element-capture/"> 7 </head> 8 9 <body> 10 <p class="instructions"> 11 When prompted, accept to give permission to use your audio, video devices. 12 </p> 13 <h1 class="instructions">Description</h1> 14 <p class="instructions"> 15 This test checks that restricting BrowserCaptureMediaStreamTrack works as 16 expected. 17 </p> 18 19 <style> 20 div { 21 height: 100px; 22 } 23 .stacking { 24 opacity: 0.9; 25 } 26 #container { 27 columns:4; 28 column-fill:auto; 29 } 30 .fragmentize { 31 height: 50px; 32 } 33 #target { 34 background: linear-gradient(red, blue); 35 } 36 </style> 37 38 39 <div id='container'> 40 <div id='target'></div> 41 </div> 42 <video id="video" 43 style="border: 2px blue dotted; width: 250px; height: 250px;" 44 autoplay playsinline muted></video> 45 46 <script src=/resources/testharness.js></script> 47 <script src=/resources/testharnessreport.js></script> 48 <script src=/resources/testdriver.js></script> 49 <script src=/resources/testdriver-vendor.js></script> 50 51 <script> 52 "use strict"; 53 54 // For more information, see: 55 // https://screen-share.github.io/element-capture/#elements-eligible-for-restriction 56 const EligibilityRequirement = { 57 StackingContext: "StackingContext", 58 OnlyOneBoxFragment: "OnlyOneBoxFragment", 59 FlattenedIn3D: "FlattenedIn3D", 60 }; 61 62 // The target div. 63 const div = document.getElementById('target'); 64 65 // Returns a promise that, if successful, will resolve to a media stream. 66 async function getDisplayMedia() { 67 return test_driver.bless('getDisplayMedia', () => 68 navigator.mediaDevices.getDisplayMedia({ 69 video: { displaySurface: "browser" }, 70 selfBrowserSurface: "include", 71 })); 72 } 73 74 // Returns a promise that will resolve successfully if at least one frame is 75 // read before the timeout. 76 function assertFrameRead(t, state, message) { 77 const last_frame_count = state.frame_count; 78 return t.step_wait(() => state.frame_count > last_frame_count, 79 message, 5000, 10); 80 } 81 82 // Returns a promise that will resolve successfully if there are no frames 83 // produced for an entire second after being called. 84 function assertStopsProducingFrames(t, state, message) { 85 let last_frame_count = state.frame_count; 86 87 return t.step_timeout(() => { 88 assert_equals(state.frame_count, last_frame_count); 89 }, 1000); 90 } 91 92 function makeDivEligible() { 93 // Must always have a stacking context to be eligible. 94 div.classList.add("stacking"); 95 div.parentElement.classList.remove("fragmented"); 96 div.style.transform = ""; 97 } 98 99 function makeDivIneligible(state) { 100 switch(state.eligibilityParam) { 101 case EligibilityRequirement.StackingContext: 102 div.classList.remove("stacking"); 103 break; 104 105 case EligibilityRequirement.OnlyOneBoxFragment: 106 div.parentElement.classList.add("fragmented"); 107 break; 108 109 case EligibilityRequirement.FlattenedIn3D: 110 div.style.transform = "rotateY(90deg)"; 111 break; 112 } 113 } 114 115 // Restore element state after each test. 116 function cleanupDiv() { 117 div.classList.remove("stacking"); 118 div.parentElement.classList.remove("fragmented"); 119 div.style.transform = ""; 120 } 121 122 function startAnimation(t, state) { 123 let count = 0; 124 function animate() { 125 if (!state.running) { 126 return; 127 } 128 count += 1; 129 div.innerText = count; 130 window.requestAnimationFrame(animate); 131 } 132 window.requestAnimationFrame(animate); 133 134 // Stop animation as part of cleanup. 135 t.add_cleanup(() => { state.running = false; }); 136 } 137 138 // Updates the state.frame_count value whenever a new frame is received on 139 // the passed in media stream track. 140 async function readFromTrack(state, track) { 141 while (state.running) { 142 const reader = new MediaStreamTrackProcessor(track).readable.getReader(); 143 while (true) { 144 const frameOrDone = await reader.read(); 145 if (frameOrDone.done) { 146 break; 147 } 148 frameOrDone.value.close(); 149 state.frame_count += 1; 150 } 151 } 152 } 153 154 // Parameterized test method. Note that this returns a Promise that will be 155 // resolved to represent success of the entire promise test. 156 async function runTest(t, eligibilityParam) { 157 let state = { 158 eligibilityParam: eligibilityParam, 159 frame_count: 0, 160 running: true, 161 reading: false, 162 }; 163 startAnimation(t, state); 164 165 let videoTrack = undefined; 166 return getDisplayMedia().then(stream => { 167 t.add_cleanup(() => { 168 stream.getTracks().forEach(track => track.stop()); 169 }); 170 assert_true(!!stream, "should have resolved to a stream."); 171 assert_true(stream.active, "stream should be active."); 172 assert_equals(stream.getVideoTracks().length, 1); 173 174 [videoTrack] = stream.getVideoTracks(); 175 assert_true(videoTrack instanceof MediaStreamTrack, 176 "track should be either MediaStreamTrack or a subclass thereof."); 177 assert_equals(videoTrack.readyState, "live", "track should be live."); 178 179 // Consume the stream in a video element. 180 const video = document.querySelector('video'); 181 video.srcObject = stream; 182 183 // Remove the video source, so that the stream object can be released. 184 t.add_cleanup(() => {video.srcObject = null}); 185 186 // Keep track of the number of frames used. 187 const readPromise = readFromTrack(state, videoTrack); 188 t.add_cleanup(() => readPromise); 189 190 return assertFrameRead(t, state, "Track should produce frames."); 191 }).then(() => { 192 assert_true(!!RestrictionTarget, "RestrictionTarget exposed."); 193 assert_true(!!RestrictionTarget.fromElement, 194 "RestrictionTarget.fromElement exposed."); 195 196 return RestrictionTarget.fromElement(div); 197 }).then(restrictionTarget => { 198 assert_true(!!videoTrack.restrictTo, "restrictTo exposed."); 199 assert_true(typeof videoTrack.restrictTo === 'function', 200 "restrictTo is a function."); 201 202 return videoTrack.restrictTo(restrictionTarget); 203 }).then(() => { 204 // By default, elements are not eligible for restriction due to not being 205 // placed in their own stacking context. 206 return assertStopsProducingFrames(t, state, 207 "No new frames after restriction."); 208 }); 209 210 // TODO(crbug.com/333770107): once the issue with the 211 // --disable-threaded-compositing flag is resolved on Chrome's check in bots 212 // the rest of this test should be enabled. 213 // ).then(() => { 214 // // Should be unpaused now that the element is eligible. 215 // makeDivEligible(); 216 217 // // Make sure the element state is restored to default between tests. 218 // t.add_cleanup(() => { cleanupDiv(); }); 219 220 // // Restart the reader now that the stream is producing frames again. 221 // return assertFrameRead(t, state, 222 // "Received at least one frame after becoming eligible."); 223 // }).then(() => { 224 225 // // Should pause if it becomes ineligible again. 226 // makeDivIneligible(state); 227 // return assertStopsProducingFrames(t, state, 228 // "No new frames after becoming ineligible again."); 229 // }); 230 } 231 232 // Test parameterizations. 233 [ 234 EligibilityRequirement.StackingContext, 235 EligibilityRequirement.OnlyOneBoxFragment, 236 EligibilityRequirement.FlattenedIn3D, 237 ].forEach(param => 238 promise_test(t => runTest(t, param), 239 `Tests that restricting MediaStreamTrack objects works as expected (${param}).` 240 )); 241 242 </script> 243 </body> 244 245 </html>