OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // This file contains a test harness for testing the behavior of pages running |
| 6 // inside off-screen tabs: |
| 7 // |
| 8 // 1. It provides a simple way to formulate a data URI containing a full HTML |
| 9 // document that executes arbitrary script. The arbitrary script running |
| 10 // in the off-screen tab calls setFillColor() to expose its current state |
| 11 // by rendering a color fill. In addition, the off-screen tab continuously |
| 12 // paints changes that force the tab capture implementation to continuously |
| 13 // capture new video frames. |
| 14 // |
| 15 // 2. In the extension's JavaScript context, a waitAnForExpectedColor() |
| 16 // function is provided to sample the video frames of the captured |
| 17 // off-screen tab until the center pixel contains one of the colors in a |
| 18 // set of expected colors. |
| 19 |
| 20 // Width/Height of off-screen tab, rendered content, and captured content. |
| 21 var width = 160; |
| 22 var height = 120; |
| 23 |
| 24 // Capture frame rate. |
| 25 var frameRate = 8; |
| 26 |
| 27 // Return a full HTML document that executes the |script|. The script calls |
| 28 // setFillColor() to expose changes to its internal state. |
| 29 function makeOffscreenTabTestDocument(script) { |
| 30 return '\ |
| 31 <html><body style="background-color:black; margin:0; padding:0;"></body>\n\ |
| 32 <script>\n\ |
| 33 var redColor = [255, 0, 0];\n\ |
| 34 var greenColor = [0, 255, 0];\n\ |
| 35 var blueColor = [0, 0, 255];\n\ |
| 36 \n\ |
| 37 var fillColor = [0, 0, 0];\n\ |
| 38 function setFillColor(color) {\n\ |
| 39 fillColor = color;\n\ |
| 40 }\n\ |
| 41 \n\ |
| 42 var debugMessage = "";\n\ |
| 43 function setDebugMessage(msg) {\n\ |
| 44 debugMessage = msg;\n\ |
| 45 }\n\ |
| 46 \n\ |
| 47 function updateTestPattern() {\n\ |
| 48 if (!this.canvas) {\n\ |
| 49 this.canvas = document.createElement("canvas");\n\ |
| 50 this.canvas.width = ' + width + ';\n\ |
| 51 this.canvas.height = ' + height + ';\n\ |
| 52 this.canvas.style.position = "absolute";\n\ |
| 53 this.canvas.style.top = "0px";\n\ |
| 54 this.canvas.style.left = "0px";\n\ |
| 55 this.canvas.style.width = "100%";\n\ |
| 56 this.canvas.style.height = "100%";\n\ |
| 57 document.body.appendChild(this.canvas);\n\ |
| 58 }\n\ |
| 59 var context = this.canvas.getContext("2d");\n\ |
| 60 // Fill with solid color.\n\ |
| 61 context.fillStyle = "rgb(" + fillColor + ")";\n\ |
| 62 context.fillRect(0, 0, this.canvas.width, this.canvas.height);\n\ |
| 63 // Draw the circle that moves around the page.\n\ |
| 64 var inverseColor =\n\ |
| 65 [255 - fillColor[0], 255 - fillColor[1], 255 - fillColor[2]];\n\ |
| 66 if (debugMessage) {\n\ |
| 67 context.fillStyle = "rgb(" + inverseColor + ")";\n\ |
| 68 context.font = "xx-small monospace";\n\ |
| 69 context.fillText(debugMessage, 0, 0);\n\ |
| 70 }\n\ |
| 71 context.fillStyle = "rgba(" + inverseColor + ", 0.5)";\n\ |
| 72 context.beginPath();\n\ |
| 73 if (!this.frameNumber) {\n\ |
| 74 this.frameNumber = 1;\n\ |
| 75 } else {\n\ |
| 76 ++this.frameNumber;\n\ |
| 77 }\n\ |
| 78 var i = this.frameNumber % 200;\n\ |
| 79 var t = (this.frameNumber + 3000) * (0.01 + i / 8000.0);\n\ |
| 80 var x = (Math.sin(t) * 0.45 + 0.5) * this.canvas.width;\n\ |
| 81 var y = (Math.cos(t * 0.9) * 0.45 + 0.5) * this.canvas.height;\n\ |
| 82 context.arc(x, y, 16, 0, 2 * Math.PI, false);\n\ |
| 83 context.closePath();\n\ |
| 84 context.fill();\n\ |
| 85 }\n\ |
| 86 \n\ |
| 87 function renderTestPatternLoop() {\n\ |
| 88 updateTestPattern();\n\ |
| 89 requestAnimationFrame(renderTestPatternLoop);\n\ |
| 90 }\n\ |
| 91 renderTestPatternLoop();\n\ |
| 92 \n' + |
| 93 script + '\n\ |
| 94 </script></html>'; |
| 95 } |
| 96 |
| 97 // Return the given |html| document as an encoded data URI. |
| 98 function makeDataUriFromDocument(html) { |
| 99 return 'data:text/html;charset=UTF-8,' + encodeURIComponent(html); |
| 100 } |
| 101 |
| 102 // Returns capture options for starting the off-screen tab. |
| 103 function getCaptureOptions() { |
| 104 return { |
| 105 video: true, |
| 106 audio: false, |
| 107 videoConstraints: { |
| 108 mandatory: { |
| 109 minWidth: width, |
| 110 minHeight: height, |
| 111 maxWidth: width, |
| 112 maxHeight: height, |
| 113 maxFrameRate: frameRate, |
| 114 } |
| 115 } |
| 116 }; |
| 117 } |
| 118 |
| 119 // Samples the video frames from a capture |stream|, testing the color of the |
| 120 // center pixel. Once the pixel matches one of the colors in |expectedColors|, |
| 121 // run |callback| with the index of the matched color. |colorDeviation| is used |
| 122 // to imprecisely match colors. |
| 123 function waitForAnExpectedColor( |
| 124 stream, expectedColors, colorDeviation, callback) { |
| 125 chrome.test.assertTrue(!!stream); |
| 126 chrome.test.assertTrue(expectedColors.length > 0); |
| 127 chrome.test.assertTrue(!!callback); |
| 128 |
| 129 var video = document.getElementById('video'); |
| 130 chrome.test.assertTrue(!!video); |
| 131 |
| 132 // If not yet done, plug the LocalMediaStream into the video element. |
| 133 if (!this.stream || this.stream != stream) { |
| 134 this.stream = stream; |
| 135 video.src = URL.createObjectURL(stream); |
| 136 video.play(); |
| 137 } |
| 138 |
| 139 // Create a canvas to sample frames from the video element. |
| 140 if (!this.canvas) { |
| 141 this.canvas = document.createElement("canvas"); |
| 142 this.canvas.width = width; |
| 143 this.canvas.height = height; |
| 144 } |
| 145 |
| 146 // Only bother examining a video frame if the video timestamp has advanced. |
| 147 var currentVideoTimestamp = video.currentTime; |
| 148 if (!this.lastVideoTimestamp || |
| 149 this.lastVideoTimestamp < currentVideoTimestamp) { |
| 150 this.lastVideoTimestamp = currentVideoTimestamp; |
| 151 |
| 152 // Grab a snapshot of the center pixel of the video. |
| 153 var ctx = this.canvas.getContext("2d"); |
| 154 ctx.drawImage(video, 0, 0, width, height); |
| 155 var imageData = ctx.getImageData(width / 2, height / 2, 1, 1); |
| 156 var pixel = [imageData.data[0], imageData.data[1], imageData.data[2]]; |
| 157 |
| 158 // Does the pixel match one of the expected colors? |
| 159 for (var i = 0, end = expectedColors.length; i < end; ++i) { |
| 160 var color = expectedColors[i]; |
| 161 if (Math.abs(pixel[0] - color[0]) <= colorDeviation && |
| 162 Math.abs(pixel[1] - color[1]) <= colorDeviation && |
| 163 Math.abs(pixel[2] - color[2]) <= colorDeviation) { |
| 164 console.debug("Observed expected color RGB(" + color + |
| 165 ") in the video as RGB(" + pixel + ")"); |
| 166 setTimeout(function () { callback(i); }, 0); |
| 167 return; |
| 168 } |
| 169 } |
| 170 } |
| 171 |
| 172 // At this point, an expected color has not been observed. Schedule another |
| 173 // check after a short delay. |
| 174 setTimeout( |
| 175 function () { |
| 176 waitForAnExpectedColor(stream, expectedColors, colorDeviation, callback); |
| 177 }, |
| 178 1000 / frameRate); |
| 179 } |
| 180 |
| 181 // Stops all the tracks in a MediaStream. |
| 182 function stopAllTracks(stream) { |
| 183 var tracks = stream.getTracks(); |
| 184 for (var i = 0, end = tracks.length; i < end; ++i) { |
| 185 tracks[i].stop(); |
| 186 } |
| 187 chrome.test.assertFalse(stream.active); |
| 188 } |
OLD | NEW |