Index: chrome/test/data/extensions/api_test/tab_capture/offscreen_test_harness.js |
diff --git a/chrome/test/data/extensions/api_test/tab_capture/offscreen_test_harness.js b/chrome/test/data/extensions/api_test/tab_capture/offscreen_test_harness.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..53fa06c0e12814217545e3c2204089528db12c41 |
--- /dev/null |
+++ b/chrome/test/data/extensions/api_test/tab_capture/offscreen_test_harness.js |
@@ -0,0 +1,188 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+// This file contains a test harness for testing the behavior of pages running |
+// inside off-screen tabs: |
+// |
+// 1. It provides a simple way to formulate a data URI containing a full HTML |
+// document that executes arbitrary script. The arbitrary script running |
+// in the off-screen tab calls setFillColor() to expose its current state |
+// by rendering a color fill. In addition, the off-screen tab continuously |
+// paints changes that force the tab capture implementation to continuously |
+// capture new video frames. |
+// |
+// 2. In the extension's JavaScript context, a waitAnForExpectedColor() |
+// function is provided to sample the video frames of the captured |
+// off-screen tab until the center pixel contains one of the colors in a |
+// set of expected colors. |
+ |
+// Width/Height of off-screen tab, rendered content, and captured content. |
+var width = 160; |
+var height = 120; |
+ |
+// Capture frame rate. |
+var frameRate = 8; |
+ |
+// Return a full HTML document that executes the |script|. The script calls |
+// setFillColor() to expose changes to its internal state. |
+function makeOffscreenTabTestDocument(script) { |
+ return '\ |
+<html><body style="background-color:black; margin:0; padding:0;"></body>\n\ |
+<script>\n\ |
+var redColor = [255, 0, 0];\n\ |
+var greenColor = [0, 255, 0];\n\ |
+var blueColor = [0, 0, 255];\n\ |
+\n\ |
+var fillColor = [0, 0, 0];\n\ |
+function setFillColor(color) {\n\ |
+ fillColor = color;\n\ |
+}\n\ |
+\n\ |
+var debugMessage = "";\n\ |
+function setDebugMessage(msg) {\n\ |
+ debugMessage = msg;\n\ |
+}\n\ |
+\n\ |
+function updateTestPattern() {\n\ |
+ if (!this.canvas) {\n\ |
+ this.canvas = document.createElement("canvas");\n\ |
+ this.canvas.width = ' + width + ';\n\ |
+ this.canvas.height = ' + height + ';\n\ |
+ this.canvas.style.position = "absolute";\n\ |
+ this.canvas.style.top = "0px";\n\ |
+ this.canvas.style.left = "0px";\n\ |
+ this.canvas.style.width = "100%";\n\ |
+ this.canvas.style.height = "100%";\n\ |
+ document.body.appendChild(this.canvas);\n\ |
+ }\n\ |
+ var context = this.canvas.getContext("2d");\n\ |
+ // Fill with solid color.\n\ |
+ context.fillStyle = "rgb(" + fillColor + ")";\n\ |
+ context.fillRect(0, 0, this.canvas.width, this.canvas.height);\n\ |
+ // Draw the circle that moves around the page.\n\ |
+ var inverseColor =\n\ |
+ [255 - fillColor[0], 255 - fillColor[1], 255 - fillColor[2]];\n\ |
+ if (debugMessage) {\n\ |
+ context.fillStyle = "rgb(" + inverseColor + ")";\n\ |
+ context.font = "xx-small monospace";\n\ |
+ context.fillText(debugMessage, 0, 0);\n\ |
+ }\n\ |
+ context.fillStyle = "rgba(" + inverseColor + ", 0.5)";\n\ |
+ context.beginPath();\n\ |
+ if (!this.frameNumber) {\n\ |
+ this.frameNumber = 1;\n\ |
+ } else {\n\ |
+ ++this.frameNumber;\n\ |
+ }\n\ |
+ var i = this.frameNumber % 200;\n\ |
+ var t = (this.frameNumber + 3000) * (0.01 + i / 8000.0);\n\ |
+ var x = (Math.sin(t) * 0.45 + 0.5) * this.canvas.width;\n\ |
+ var y = (Math.cos(t * 0.9) * 0.45 + 0.5) * this.canvas.height;\n\ |
+ context.arc(x, y, 16, 0, 2 * Math.PI, false);\n\ |
+ context.closePath();\n\ |
+ context.fill();\n\ |
+}\n\ |
+\n\ |
+function renderTestPatternLoop() {\n\ |
+ updateTestPattern();\n\ |
+ requestAnimationFrame(renderTestPatternLoop);\n\ |
+}\n\ |
+renderTestPatternLoop();\n\ |
+\n' + |
+script + '\n\ |
+</script></html>'; |
+} |
+ |
+// Return the given |html| document as an encoded data URI. |
+function makeDataUriFromDocument(html) { |
+ return 'data:text/html;charset=UTF-8,' + encodeURIComponent(html); |
+} |
+ |
+// Returns capture options for starting the off-screen tab. |
+function getCaptureOptions() { |
+ return { |
+ video: true, |
+ audio: false, |
+ videoConstraints: { |
+ mandatory: { |
+ minWidth: width, |
+ minHeight: height, |
+ maxWidth: width, |
+ maxHeight: height, |
+ maxFrameRate: frameRate, |
+ } |
+ } |
+ }; |
+} |
+ |
+// Samples the video frames from a capture |stream|, testing the color of the |
+// center pixel. Once the pixel matches one of the colors in |expectedColors|, |
+// run |callback| with the index of the matched color. |colorDeviation| is used |
+// to imprecisely match colors. |
+function waitForAnExpectedColor( |
+ stream, expectedColors, colorDeviation, callback) { |
+ chrome.test.assertTrue(!!stream); |
+ chrome.test.assertTrue(expectedColors.length > 0); |
+ chrome.test.assertTrue(!!callback); |
+ |
+ var video = document.getElementById('video'); |
+ chrome.test.assertTrue(!!video); |
+ |
+ // If not yet done, plug the LocalMediaStream into the video element. |
+ if (!this.stream || this.stream != stream) { |
+ this.stream = stream; |
+ video.src = URL.createObjectURL(stream); |
+ video.play(); |
+ } |
+ |
+ // Create a canvas to sample frames from the video element. |
+ if (!this.canvas) { |
+ this.canvas = document.createElement("canvas"); |
+ this.canvas.width = width; |
+ this.canvas.height = height; |
+ } |
+ |
+ // Only bother examining a video frame if the video timestamp has advanced. |
+ var currentVideoTimestamp = video.currentTime; |
+ if (!this.lastVideoTimestamp || |
+ this.lastVideoTimestamp < currentVideoTimestamp) { |
+ this.lastVideoTimestamp = currentVideoTimestamp; |
+ |
+ // Grab a snapshot of the center pixel of the video. |
+ var ctx = this.canvas.getContext("2d"); |
+ ctx.drawImage(video, 0, 0, width, height); |
+ var imageData = ctx.getImageData(width / 2, height / 2, 1, 1); |
+ var pixel = [imageData.data[0], imageData.data[1], imageData.data[2]]; |
+ |
+ // Does the pixel match one of the expected colors? |
+ for (var i = 0, end = expectedColors.length; i < end; ++i) { |
+ var color = expectedColors[i]; |
+ if (Math.abs(pixel[0] - color[0]) <= colorDeviation && |
+ Math.abs(pixel[1] - color[1]) <= colorDeviation && |
+ Math.abs(pixel[2] - color[2]) <= colorDeviation) { |
+ console.debug("Observed expected color RGB(" + color + |
+ ") in the video as RGB(" + pixel + ")"); |
+ setTimeout(function () { callback(i); }, 0); |
+ return; |
+ } |
+ } |
+ } |
+ |
+ // At this point, an expected color has not been observed. Schedule another |
+ // check after a short delay. |
+ setTimeout( |
+ function () { |
+ waitForAnExpectedColor(stream, expectedColors, colorDeviation, callback); |
+ }, |
+ 1000 / frameRate); |
+} |
+ |
+// Stops all the tracks in a MediaStream. |
+function stopAllTracks(stream) { |
+ var tracks = stream.getTracks(); |
+ for (var i = 0, end = tracks.length; i < end; ++i) { |
+ tracks[i].stop(); |
+ } |
+ chrome.test.assertFalse(stream.active); |
+} |