Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / test / data / extensions / api_test / tab_capture / end_to_end.js
blob47c23772f4c142c51c67e530dd5a439a69c6da8b
1 // Copyright 2014 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.
5 // The tests here cover the end-to-end functionality of tab capturing and
6 // playback as video.  The page generates video test patterns that rotate
7 // cyclicly, and the rendering output of the tab is captured into a
8 // LocalMediaStream.  This stream is then piped into a video element for
9 // playback, and a canvas is used to examine the frames of the video for
10 // expected content.  The stream may be plumbed one of two ways, depending on
11 // the 'method' query param:
13 //   local:  LocalMediaStream --> DOM Video Element
14 //   webrtc: LocalMediaStream --> PeerConnection (sender)
15 //             --> PeerConnection (receiver) --> DOM Video Element
17 // The test pattern cycles as a color fill of red, then green, then blue.
18 var colors = [ [ 255, 0, 0 ], [ 0, 255, 0 ], [ 0, 0, 255 ] ];
19 var curIdx = 0;
21 // Capture parameters.
22 var width = 64;
23 var height = 48;
24 var frameRate = 15;
26 // The stream to playback in the video element.
27 var receiveStream = null;
29 // waitForExpectedColors() removes elements from this array as each is observed.
30 // When it becomes empty, the test succeeds.
31 var expectedColors = [ [ 255, 0, 0 ], [ 0, 255, 0 ], [ 0, 0, 255 ] ];
33 function updateTestPattern() {
34   if (!this.canvas) {
35     this.canvas = document.createElement("canvas");
36     this.canvas.width = 320;
37     this.canvas.height = 200;
38     this.canvas.style.position = "absolute";
39     this.canvas.style.top = "0px";
40     this.canvas.style.left = "0px";
41     this.canvas.style.width = "100%";
42     this.canvas.style.height = "100%";
43     document.body.appendChild(this.canvas);
44   }
45   var context = this.canvas.getContext("2d");
46   // Fill with solid color.
47   context.fillStyle = "rgb(" + colors[curIdx] + ")";
48   context.fillRect(0, 0, this.canvas.width, this.canvas.height);
49   // Draw the circle that moves around the page.
50   context.fillStyle = "rgb(" + colors[(curIdx + 1) % colors.length] + ")";
51   context.beginPath();
52   if (!this.frameNumber) {
53     this.frameNumber = 1;
54   } else {
55     ++this.frameNumber;
56   }
57   var i = this.frameNumber % 200;
58   var t = (this.frameNumber + 3000) * (0.01 + i / 8000.0);
59   var x = (Math.sin(t) * 0.45 + 0.5) * this.canvas.width;
60   var y = (Math.cos(t * 0.9) * 0.45 + 0.5) * this.canvas.height;
61   context.arc(x, y, 16, 0, 2 * Math.PI, false);
62   context.closePath();
63   context.fill();
66 function renderTestPatternLoop() {
67   requestAnimationFrame(renderTestPatternLoop);
68   updateTestPattern();
70   if (!this.stepTimeMillis) {
71     this.stepTimeMillis = 100;
72   }
73   var now = new Date().getTime();
74   if (!this.nextSteppingAt) {
75     this.nextSteppingAt = now + this.stepTimeMillis;
76   } else if (now >= this.nextSteppingAt) {
77     ++curIdx;
78     if (curIdx >= colors.length) {  // Completed a cycle.
79       curIdx = 0;
80       // Increase the wait time between switching test patterns for overloaded
81       // bots that aren't capturing all the frames of video.
82       this.stepTimeMillis *= 1.25;
83     }
84     this.nextSteppingAt = now + this.stepTimeMillis;
85   }
88 function waitForExpectedColors(colorDeviation) {
89   // If needed, create the video and canvas elements, but no need to append them
90   // to the DOM.
91   if (!this.video) {
92     this.video = document.createElement("video");
93     this.video.width = width;
94     this.video.height = height;
95     this.video.addEventListener("error", chrome.test.fail);
96     this.video.src = URL.createObjectURL(receiveStream);
97     this.video.play();
99     this.readbackCanvas = document.createElement("canvas");
100     this.readbackCanvas.width = width;
101     this.readbackCanvas.height = height;
102   }
104   // Only bother examining a video frame if the video timestamp has advanced.
105   var currentVideoTimestamp = this.video.currentTime;
106   if (!this.lastVideoTimestamp ||
107       this.lastVideoTimestamp < currentVideoTimestamp) {
108     this.lastVideoTimestamp = currentVideoTimestamp;
110     // Grab a snapshot of the center pixel of the video.
111     var ctx = this.readbackCanvas.getContext("2d");
112     ctx.drawImage(video, 0, 0, width, height);
113     var imageData = ctx.getImageData(width / 2, height / 2, 1, 1);
114     var pixel = [ imageData.data[0], imageData.data[1], imageData.data[2] ];
116     // Does the pixel match one of the expected colors?
117     for (var i = 0; i < expectedColors.length; ++i) {
118       var curColor = expectedColors[i];
119       if (Math.abs(pixel[0] - curColor[0]) <= colorDeviation &&
120           Math.abs(pixel[1] - curColor[1]) <= colorDeviation &&
121           Math.abs(pixel[2] - curColor[2]) <= colorDeviation) {
122         console.debug("Observed expected color RGB(" + curColor +
123             ") in the video as RGB(" + pixel + ")");
124         expectedColors.splice(i, 1);
125       }
126     }
127   }
129   if (expectedColors.length == 0) {
130     chrome.test.succeed();
131   } else {
132     setTimeout(function () { waitForExpectedColors(colorDeviation); },
133                1000 / frameRate);
134   }
137 chrome.test.runTests([
138   function endToEndTest() {
139     // The receive port changes between browser_test invocations, and is passed
140     // as an query parameter in the URL.
141     var transportMethod;  // Should be: local or webrtc.
142     var colorDeviation;  // How far from the expected intensity ([0,255] scale)?
143     try {
144       transportMethod = window.location.search.match(/(\?|&)method=(\w+)/)[2];
145       chrome.test.assertTrue(transportMethod == 'local' ||
146                              transportMethod == 'webrtc');
147       colorDeviation = parseInt(
148           window.location.search.match(/(\?|&)colorDeviation=(\d+)/)[2]);
149       chrome.test.assertTrue(colorDeviation >= 0 && colorDeviation <= 255);
150     } catch (err) {
151       chrome.test.fail("Error parsing query params -- " + err.message);
152       return;
153     }
155     // Start rendering test patterns.
156     renderTestPatternLoop();
158     chrome.tabCapture.capture(
159         { video: true,
160           audio: true,
161           videoConstraints: {
162             mandatory: {
163               minWidth: width,
164               minHeight: height,
165               maxWidth: width,
166               maxHeight: height,
167               maxFrameRate: frameRate,
168             }
169           }
170         },
171         function remoteTheStream(captureStream) {
172           chrome.test.assertTrue(!!captureStream);
173           if (transportMethod == 'local') {
174             receiveStream = captureStream;
175             waitForExpectedColors(colorDeviation);
176           } else if (transportMethod == 'webrtc') {
177             var sender = new webkitRTCPeerConnection(null);
178             var receiver = new webkitRTCPeerConnection(null);
179             sender.onicecandidate = function (event) {
180               if (event.candidate) {
181                 receiver.addIceCandidate(new RTCIceCandidate(event.candidate));
182               }
183             };
184             receiver.onicecandidate = function (event) {
185               if (event.candidate) {
186                 sender.addIceCandidate(new RTCIceCandidate(event.candidate));
187               }
188             };
189             receiver.onaddstream = function (event) {
190               receiveStream = event.stream;
191               waitForExpectedColors(colorDeviation);
192             };
193             sender.addStream(captureStream);
194             sender.createOffer(function (sender_description) {
195               sender.setLocalDescription(sender_description);
196               receiver.setRemoteDescription(sender_description);
197               receiver.createAnswer(function (receiver_description) {
198                 receiver.setLocalDescription(receiver_description);
199                 sender.setRemoteDescription(receiver_description);
200               });
201             });
202           } else {
203             chrome.test.fail("Unknown transport method: " + transportMethod);
204           }
205         });
206   }
209 // TODO(miu): Once the WebAudio API is finalized, we should add a test to emit a
210 // tone from the sender page, and have the receiver page check for the audio
211 // tone.