blacksilence.js (3989B)
1 (function (global) { 2 "use strict"; 3 4 // an invertible check on the condition. 5 // if the constraint is applied, then the check is direct 6 // if not applied, then the result should be reversed 7 function check(constraintApplied, condition, message) { 8 var good = constraintApplied ? condition : !condition; 9 message = 10 (constraintApplied ? "with" : "without") + 11 " constraint: should " + 12 (constraintApplied ? "" : "not ") + 13 message + 14 " = " + 15 (good ? "OK" : "waiting..."); 16 info(message); 17 return good; 18 } 19 20 function mkElement(type) { 21 // This makes an unattached element. 22 // It's not rendered to save the cycles that costs on b2g emulator 23 // and it gets dropped (and GC'd) when the test is done. 24 var e = document.createElement(type); 25 e.width = 32; 26 e.height = 24; 27 document.getElementById("display").appendChild(e); 28 return e; 29 } 30 31 // Runs checkFunc until it reports success. 32 // This is kludgy, but you have to wait for media to start flowing, and it 33 // can't be any old media, it has to include real data, for which we have no 34 // reliable signals to use as a trigger. 35 function periodicCheck(checkFunc) { 36 var resolve; 37 var done = false; 38 // This returns a function so that we create 10 closures in the loop, not 39 // one; and so that the timers don't all start straight away 40 var waitAndCheck = counter => () => { 41 if (done) { 42 return Promise.resolve(); 43 } 44 return new Promise(r => setTimeout(r, 200 << counter)).then(() => { 45 if (checkFunc()) { 46 done = true; 47 resolve(); 48 } 49 }); 50 }; 51 52 var chain = Promise.resolve(); 53 for (var i = 0; i < 10; ++i) { 54 chain = chain.then(waitAndCheck(i)); 55 } 56 return new Promise(r => (resolve = r)); 57 } 58 59 function isSilence(audioData) { 60 var silence = true; 61 for (var i = 0; i < audioData.length; ++i) { 62 if (audioData[i] !== 128) { 63 silence = false; 64 } 65 } 66 return silence; 67 } 68 69 function checkAudio(constraintApplied, stream) { 70 var audio = mkElement("audio"); 71 audio.srcObject = stream; 72 audio.play(); 73 74 var context = new AudioContext(); 75 var source = context.createMediaStreamSource(stream); 76 var analyser = context.createAnalyser(); 77 source.connect(analyser); 78 analyser.connect(context.destination); 79 80 return periodicCheck(() => { 81 var sampleCount = analyser.frequencyBinCount; 82 info("got some audio samples: " + sampleCount); 83 var buffer = new Uint8Array(sampleCount); 84 analyser.getByteTimeDomainData(buffer); 85 86 var silent = check( 87 constraintApplied, 88 isSilence(buffer), 89 "be silence for audio" 90 ); 91 return sampleCount > 0 && silent; 92 }).then(() => { 93 source.disconnect(); 94 analyser.disconnect(); 95 audio.pause(); 96 ok(true, "audio is " + (constraintApplied ? "" : "not ") + "silent"); 97 }); 98 } 99 100 function checkVideo(constraintApplied, stream) { 101 var video = mkElement("video"); 102 video.srcObject = stream; 103 video.play(); 104 105 return periodicCheck(() => { 106 try { 107 var canvas = mkElement("canvas"); 108 var ctx = canvas.getContext("2d"); 109 // Have to guard drawImage with the try as well, due to bug 879717. If 110 // we get an error, this round fails, but that failure is usually just 111 // transitory. 112 ctx.drawImage(video, 0, 0); 113 ctx.getImageData(0, 0, 1, 1); 114 return check( 115 constraintApplied, 116 false, 117 "throw on getImageData for video" 118 ); 119 } catch (e) { 120 return check( 121 constraintApplied, 122 e.name === "SecurityError", 123 "get a security error: " + e.name 124 ); 125 } 126 }).then(() => { 127 video.pause(); 128 ok(true, "video is " + (constraintApplied ? "" : "not ") + "protected"); 129 }); 130 } 131 132 global.audioIsSilence = checkAudio; 133 global.videoIsBlack = checkVideo; 134 })(this);