summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/mediacapture-image
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/mediacapture-image')
-rw-r--r--testing/web-platform/tests/mediacapture-image/ImageCapture-MediaTrackSupportedConstraints.https.html29
-rw-r--r--testing/web-platform/tests/mediacapture-image/ImageCapture-creation.https.html100
-rw-r--r--testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame-crash.html11
-rw-r--r--testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame.html46
-rw-r--r--testing/web-platform/tests/mediacapture-image/ImageCapture-track.html31
-rw-r--r--testing/web-platform/tests/mediacapture-image/META.yml4
-rw-r--r--testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-fast.html67
-rw-r--r--testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.https.html161
-rw-r--r--testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-reject.https.html76
-rw-r--r--testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints.https.html112
-rw-r--r--testing/web-platform/tests/mediacapture-image/MediaStreamTrack-clone.https.html371
-rw-r--r--testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities-fast.html29
-rw-r--r--testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities.https.html159
-rw-r--r--testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getConstraints.https.html63
-rw-r--r--testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings-fast.html29
-rw-r--r--testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings.https.html89
-rw-r--r--testing/web-platform/tests/mediacapture-image/detached-HTMLCanvasElement.html26
-rw-r--r--testing/web-platform/tests/mediacapture-image/getPhotoCapabilities.html73
-rw-r--r--testing/web-platform/tests/mediacapture-image/getPhotoSettings.html61
-rw-r--r--testing/web-platform/tests/mediacapture-image/getusermedia.https.html26
-rw-r--r--testing/web-platform/tests/mediacapture-image/idlharness.window.js25
-rw-r--r--testing/web-platform/tests/mediacapture-image/resources/imagecapture-helpers.js55
-rw-r--r--testing/web-platform/tests/mediacapture-image/takePhoto-reject.html51
-rw-r--r--testing/web-platform/tests/mediacapture-image/takePhoto-with-PhotoSettings.html63
-rw-r--r--testing/web-platform/tests/mediacapture-image/takePhoto-without-PhotoCapabilities.https.window.js6
-rw-r--r--testing/web-platform/tests/mediacapture-image/takePhoto.html61
26 files changed, 1824 insertions, 0 deletions
diff --git a/testing/web-platform/tests/mediacapture-image/ImageCapture-MediaTrackSupportedConstraints.https.html b/testing/web-platform/tests/mediacapture-image/ImageCapture-MediaTrackSupportedConstraints.https.html
new file mode 100644
index 0000000000..fddeb60d0c
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/ImageCapture-MediaTrackSupportedConstraints.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// Tests that getSupportedConstraints() returns what it should.
+test(function() {
+ supported_constraints = navigator.mediaDevices.getSupportedConstraints();
+
+ assert_true(supported_constraints.whiteBalanceMode);
+ assert_true(supported_constraints.exposureMode);
+ assert_true(supported_constraints.focusMode);
+ assert_true(supported_constraints.pointsOfInterest);
+ assert_true(supported_constraints.exposureCompensation);
+ assert_true(supported_constraints.exposureTime);
+ assert_true(supported_constraints.colorTemperature);
+ assert_true(supported_constraints.iso);
+ assert_true(supported_constraints.brightness);
+ assert_true(supported_constraints.contrast);
+ assert_true(supported_constraints.saturation);
+ assert_true(supported_constraints.sharpness);
+ assert_true(supported_constraints.focusDistance);
+ assert_true(supported_constraints.pan);
+ assert_true(supported_constraints.tilt);
+ assert_true(supported_constraints.zoom);
+ assert_true(supported_constraints.torch);
+}, 'Image Capture supported constraints');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/ImageCapture-creation.https.html b/testing/web-platform/tests/mediacapture-image/ImageCapture-creation.https.html
new file mode 100644
index 0000000000..81a503893a
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/ImageCapture-creation.https.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src='../mediacapture-streams/permission-helper.js'></script>
+<script>
+
+// This test verifies that ImageCapture can be created (or not) with different
+// Media Stream Track types (audio, video).
+
+function makeAsyncTest(modifyTrack, message) {
+ async_test(function(test) {
+
+ const gotStream = test.step_func(function(stream) {
+ assert_equals(stream.getAudioTracks().length, 0);
+ assert_equals(stream.getVideoTracks().length, 1);
+
+ var videoTrack = stream.getVideoTracks()[0];
+ assert_equals(videoTrack.readyState, 'live');
+ assert_true(videoTrack.enabled);
+ assert_false(videoTrack.muted);
+
+ var capturer = new ImageCapture(videoTrack);
+ assert_equals(capturer.track, videoTrack);
+
+ modifyTrack(videoTrack);
+
+ promise_rejects_dom(test,
+ 'InvalidStateError',
+ capturer.grabFrame(),
+ 'Should throw InvalidStateError.')
+ .then(() => test.done());
+ });
+
+ const onError = test.unreached_func('Error creating MediaStream.');
+ // both permissions are needed at some point, asking for both at once
+ setMediaPermission()
+ .then(() => navigator.mediaDevices.getUserMedia({video: true}))
+ .then(gotStream)
+ .catch(onError);
+
+ }, message);
+}
+
+var disableTrack = function(videoTrack) {
+ // grabFrame() is rejected if the associated video track is disabled.
+ videoTrack.enabled = false;
+};
+
+var stopTrack = function(videoTrack) {
+ // grabFrame() is rejected if the associated video track is ended.
+ videoTrack.stop();
+ assert_equals(videoTrack.readyState, 'ended');
+};
+
+// Create the rejection tests. Note that grabFrame() would also be rejected if
+// the video Track was muted but that's a read-only property of the Track.
+makeAsyncTest(disableTrack, 'grabFrame() of a disabled Track');
+makeAsyncTest(stopTrack, 'grabFrame() of an ended Track');
+
+
+var testAudio = async_test(function() {
+ navigator.mediaDevices.getUserMedia({audio:true})
+ .then(
+ this.step_func(function(stream) {
+ assert_equals(stream.getAudioTracks().length, 1);
+ assert_equals(stream.getVideoTracks().length, 0);
+ assert_throws_dom("NotSupportedError",
+ function() {
+ var capturer = new ImageCapture(stream.getAudioTracks()[0]);
+ },
+ 'an ImageCapturer can only be created from a video track');
+
+ this.done();
+ }))
+ .catch(
+ this.unreached_func('Error creating MediaStream.'));
+}, 'verifies that an ImageCapture cannot be created out of an Audio Track');
+
+var testParameter = test(function() {
+ const invalidParameters = [
+ "invalid",
+ null,
+ 123,
+ {},
+ "",
+ true
+ ];
+ assert_throws_js(TypeError,
+ function() { var capturer = new ImageCapture(); },
+ 'an ImageCapturer cannot be created with no parameter');
+ invalidParameters.map(parameter => {
+ assert_throws_js(TypeError,
+ function() { var capturer = new ImageCapture(parameter); },
+ `an ImageCapturer cannot be created with a ${parameter} parameter`);
+ });
+}, 'throw "TypeError" if parameter is not MediaStreamTrack.');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame-crash.html b/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame-crash.html
new file mode 100644
index 0000000000..d4bf57f0fa
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame-crash.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Repro of https://crbug.com/1374294</title>
+<body></body>
+<script>
+ let iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ let generator = new iframe.contentWindow.MediaStreamTrackGenerator({kind: 'video'});
+ const capture = new ImageCapture(generator);
+ capture.grabFrame();
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame.html b/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame.html
new file mode 100644
index 0000000000..a9a6930aaf
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<canvas id='canvas0' width=10 height=10></canvas>
+<canvas id='canvas1' width=10 height=10></canvas>
+</body>
+<script>
+
+// This test verifies that ImageCapture can grabFrame()s.
+
+var test = async_test(function() {
+ var canvas0 = document.getElementById('canvas0');
+ var context0 = canvas0.getContext("2d");
+ context0.fillStyle = "red";
+ context0.fillRect(0, 0, 10, 10);
+
+ var stream = canvas0.captureStream();
+
+ var capturer = new ImageCapture(stream.getVideoTracks()[0]);
+
+ capturer.grabFrame()
+ .then(bitmap => {
+ assert_equals(canvas0.width, bitmap.width);
+ assert_equals(canvas0.height, bitmap.height);
+
+ var context1 = document.getElementById('canvas1').getContext("2d");
+ context1.drawImage(bitmap, 0, 0);
+
+ var imageData0 = context0.getImageData(0, 0, 10, 10);
+ var imageData1 = context1.getImageData(0, 0, 10, 10);
+
+ assert_equals(imageData0.width, imageData1.width);
+ assert_equals(imageData0.height, imageData1.height);
+ assert_equals(imageData0.data.length, imageData1.data.length);
+ for (var i = 0; i < imageData0.data.length; i++)
+ assert_approx_equals(imageData0.data[i], imageData1.data[i], 5);
+
+ this.done();
+ })
+ .catch(error => {
+ assert_unreached('Error during grabFrame(): '+ error);
+ });
+}, 'exercises the ImageCapture API creation and grabFrame().');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/ImageCapture-track.html b/testing/web-platform/tests/mediacapture-image/ImageCapture-track.html
new file mode 100644
index 0000000000..774970ad0c
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/ImageCapture-track.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>ImageCapture track</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<link rel="help" href="https://w3c.github.io/mediacapture-image/#dom-imagecapture-track">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<canvas id='canvas' width=10 height=10></canvas>
+<script>
+
+test(t => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+
+ let stream = canvas.captureStream();
+ let videoTrack = stream.getVideoTracks()[0];
+
+ let capturer = new ImageCapture(videoTrack);
+ assert_true(capturer.track instanceof MediaStreamTrack);
+ assert_equals(capturer.track, videoTrack);
+
+ let cloneTrack = videoTrack.clone();
+ assert_not_equals(videoTrack, cloneTrack);
+
+ capturer.track = cloneTrack;
+ assert_equals(capturer.track, videoTrack);
+}, "ImageCapture track attribute is readonly")
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/META.yml b/testing/web-platform/tests/mediacapture-image/META.yml
new file mode 100644
index 0000000000..1b4fef5e5a
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/META.yml
@@ -0,0 +1,4 @@
+spec: https://w3c.github.io/mediacapture-image/
+suggested_reviewers:
+ - yellowdoge
+ - reillyeon
diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-fast.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-fast.html
new file mode 100644
index 0000000000..7921798017
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-fast.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<canvas id='canvas' width=10 height=10></canvas>
+</body>
+<script>
+
+// This test verifies that MediaStreamTrack.applyConstraints() exists and that,
+// when called with no parameters, returns a Promise that is resolved. This
+// might not make sense: https://github.com/w3c/mediacapture-main/issues/438 .
+// Other tests go deeper.
+promise_test(function(t) {
+ var canvas = document.getElementById('canvas');
+ var context = canvas.getContext("2d");
+ context.fillStyle = "red";
+ context.fillRect(0, 0, 10, 10);
+
+ var stream = canvas.captureStream();
+ assert_equals(stream.getAudioTracks().length, 0);
+ assert_equals(stream.getVideoTracks().length, 1);
+
+ var videoTrack = stream.getVideoTracks()[0];
+ return videoTrack.applyConstraints();
+}, 'MediaStreamTrack.applyConstraints()');
+
+// This test verifies that MediaStreamTrack.applyConstraints() exists and that,
+// when called with an empty advanced constraint set, returns a Promise that is
+// resolved.
+promise_test(function(t) {
+ var canvas = document.getElementById('canvas');
+ var context = canvas.getContext("2d");
+ context.fillStyle = "red";
+ context.fillRect(0, 0, 10, 10);
+
+ var stream = canvas.captureStream();
+ assert_equals(stream.getAudioTracks().length, 0);
+ assert_equals(stream.getVideoTracks().length, 1);
+
+ var videoTrack = stream.getVideoTracks()[0];
+ return videoTrack.applyConstraints({advanced: []});
+}, 'MediaStreamTrack.applyConstraints({advanced: []})');
+
+// This test verifies that applyConstraints() rejects the returned Promise if
+// passed a non-supported image-capture constraint (https://crbug.com/711694).
+promise_test(async function(t) {
+ var canvas = document.getElementById('canvas');
+ var context = canvas.getContext("2d");
+ context.fillStyle = "red";
+ context.fillRect(0, 0, 10, 10);
+
+ var stream = canvas.captureStream();
+ var videoTrack = stream.getVideoTracks()[0];
+
+ // Use e.g. |torch| as an example of unsupported constraint.
+ assert_false("torch" in videoTrack.getCapabilities());
+ try {
+ await videoTrack.applyConstraints({torch: {exact: true}});
+ } catch (error) {
+ assert_equals(error.name, 'OverconstrainedError');
+ assert_equals(error.constraint, 'torch');
+ return;
+ }
+ assert_unreached('expected applyConstraints to reject');
+}, 'MediaStreamTrack.applyConstraints() with unsupported constraint');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.https.html
new file mode 100644
index 0000000000..e18480aae1
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.https.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<script>
+
+// This test verifies that the |constraints| configured in the mock Mojo
+// service implementation, are returned by MediaStreamTrack.getSettings().
+
+image_capture_test(async t => {
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ 'granted');
+
+ const constraints = { advanced : [{ whiteBalanceMode : 'single-shot',
+ exposureMode : 'manual',
+ focusMode : 'single-shot',
+
+ pointsOfInterest : [{x : 0.1, y : 0.2},
+ {x : 0.3, y : 0.4}],
+
+ exposureCompensation : 133.77,
+ // in nano-seconds.
+ exposureTime : 10000,
+ colorTemperature : 6000,
+ iso : 120.0,
+
+ brightness : 3,
+ contrast : 4,
+ saturation : 5,
+ sharpness : 6,
+ focusDistance : 7,
+
+ pan : 8,
+ tilt : 9,
+ zoom : 3.141592,
+
+ torch : true
+ }]};
+
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let videoTrack = stream.getVideoTracks()[0];
+
+ try {
+ await videoTrack.applyConstraints(constraints);
+ } catch (error) {
+ assert_unreached('Error applying constraints: ' + error.message);
+ }
+
+ let settings = videoTrack.getSettings();
+ assert_equals(typeof settings, 'object');
+
+ assert_equals(constraints.advanced[0].whiteBalanceMode,
+ settings.whiteBalanceMode, 'whiteBalanceMode');
+ assert_equals(constraints.advanced[0].exposureMode, settings.exposureMode,
+ 'exposureMode');
+ assert_equals(constraints.advanced[0].focusMode, settings.focusMode,
+ 'focusMode');
+
+ assert_point2d_array_approx_equals(
+ constraints.advanced[0].pointsOfInterest, settings.pointsOfInterest,
+ 0.01);
+
+ assert_equals(constraints.advanced[0].exposureCompensation,
+ settings.exposureCompensation, 'exposureCompensation');
+ assert_equals(constraints.advanced[0].exposureTime,
+ settings.exposureTime, 'exposureTime');
+ assert_equals(constraints.advanced[0].colorTemperature,
+ settings.colorTemperature, 'colorTemperature');
+ assert_equals(constraints.advanced[0].iso, settings.iso, 'iso');
+
+ assert_equals(constraints.advanced[0].brightness, settings.brightness,
+ 'brightness');
+ assert_equals(constraints.advanced[0].contrast, settings.contrast,
+ 'contrast');
+ assert_equals(constraints.advanced[0].saturation, settings.saturation,
+ 'saturation');
+ assert_equals(constraints.advanced[0].sharpness, settings.sharpness,
+ 'sharpness');
+
+ assert_equals(constraints.advanced[0].focusDistance, settings.focusDistance,
+ 'focusDistance');
+
+ assert_equals(constraints.advanced[0].pan, settings.pan, 'pan');
+ assert_equals(constraints.advanced[0].tilt, settings.tilt, 'tilt');
+ assert_equals(constraints.advanced[0].zoom, settings.zoom, 'zoom');
+
+ assert_equals(constraints.advanced[0].torch, settings.torch, 'torch');
+
+}, 'exercises an applyConstraints() - getSettings() cycle with PTZ permission granted');
+
+
+// This test verifies that the PTZ |constraints| configured in the mock Mojo
+// service implementation can't be applied if PTZ permission is denied.
+
+image_capture_test(async t => {
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ 'denied');
+
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let videoTrack = stream.getVideoTracks()[0];
+
+ const constraints = [{ pan: { exact: 8 } }, { tilt: { exact: 9 } }];
+ await Promise.all(constraints.map(async constraint => {
+ try {
+ await videoTrack.applyConstraints(constraint);
+ } catch (error) {
+ assert_equals(error.name, 'OverconstrainedError');
+ assert_equals(error.constraint, Object.keys(constraint)[0]);
+ return;
+ }
+ assert_unreached(
+ "applyConstraints should throw an OverconstrainedError for " +
+ JSON.stringify(constraint));
+ }));
+
+}, 'exercises an applyConstraints() with required constraints with PTZ permission denied');
+
+image_capture_test(async t => {
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ 'denied');
+
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let videoTrack = stream.getVideoTracks()[0];
+
+ const constraints = [{ pan: { ideal: 8 } }, { tilt: { ideal: 9 } }];
+ await Promise.all(constraints.map(async constraint => {
+ try {
+ await videoTrack.applyConstraints(constraint);
+ } catch (error) {
+ assert_unreached(
+ `applyConstraints should not throw an ${error.name} for ` +
+ JSON.stringify(constraint));
+ }
+ }));
+
+}, 'exercises an applyConstraints() with ideal constraints with PTZ permission denied');
+
+image_capture_test(async t => {
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ 'denied');
+
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let videoTrack = stream.getVideoTracks()[0];
+
+ const advanced_constraints = [{ pan: 8 }, { tilt: 9 }];
+ await Promise.all(advanced_constraints.map(async advanced_constraint => {
+ const constraint = { advanced: [advanced_constraint] };
+ try {
+ await videoTrack.applyConstraints(constraint);
+ } catch (error) {
+ assert_unreached(
+ `applyConstraints should not throw an ${error.name} for ` +
+ JSON.stringify(constraint));
+ }
+ }));
+
+}, 'exercises an applyConstraints() with advances constraints with PTZ permission denied');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-reject.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-reject.https.html
new file mode 100644
index 0000000000..0bb4c9f74e
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-reject.https.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<script>
+
+// This test verifies that MediaStreamTrack.applyConstraints() rejects if any
+// passed constraint is unsupported or outside its allowed range.
+var makePromiseTest = function(getConstraint) {
+ image_capture_test(async (t, imageCaptureTest) => {
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ 'granted');
+
+ imageCaptureTest.mockImageCapture().state().supportsTorch = false;
+
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let videoTrack = stream.getVideoTracks()[0];
+
+ const constraint = getConstraint(imageCaptureTest.mockImageCapture().state());
+ try {
+ await videoTrack.applyConstraints(constraint);
+ assert_unreached('expected applyConstraints to reject');
+ } catch (error) {
+ assert_equals(error.name, 'OverconstrainedError');
+ assert_equals(error.constraint, Object.keys(constraint)[0]);
+ }
+ });
+};
+
+const constraintGenerators = [
+ capabilities => ({ whiteBalanceMode: { exact: 'manual' } }),
+ capabilities => ({ exposureMode: { exact: 'none' } }),
+ capabilities => ({ focusMode: { exact: 'continuous' } }),
+ capabilities => ({
+ exposureCompensation: { exact: capabilities.exposureCompensation.max + 1 }
+ }),
+ capabilities => ({
+ exposureCompensation: { exact: capabilities.exposureCompensation.min - 1 }
+ }),
+ capabilities => ({
+ colorTemperature: { exact: capabilities.colorTemperature.max + 1 }
+ }),
+ capabilities => ({
+ colorTemperature: { exact: capabilities.colorTemperature.min - 1 }
+ }),
+ capabilities => ({ iso: { exact: capabilities.iso.max + 1 } }),
+ capabilities => ({ iso: { exact: capabilities.iso.min - 1 } }),
+ capabilities => ({ brightness: { exact: capabilities.brightness.max + 1 } }),
+ capabilities => ({ brightness: { exact: capabilities.brightness.min - 1 } }),
+ capabilities => ({ contrast: { exact: capabilities.contrast.max + 1 } }),
+ capabilities => ({ contrast: { exact: capabilities.contrast.min - 1 } }),
+ capabilities => ({ saturation: { exact: capabilities.saturation.max + 1 } }),
+ capabilities => ({ saturation: { exact: capabilities.saturation.min - 1 } }),
+ capabilities => ({ sharpness: { exact: capabilities.sharpness.max + 1 } }),
+ capabilities => ({ sharpness: { exact: capabilities.sharpness.min - 1 } }),
+ capabilities => ({ pan: { exact: capabilities.pan.max + 1 } }),
+ capabilities => ({ pan: { exact: capabilities.pan.min - 1 } }),
+ capabilities => ({ tilt: { exact: capabilities.tilt.max + 1 } }),
+ capabilities => ({ tilt: { exact: capabilities.tilt.min - 1 } }),
+ capabilities => ({ zoom: { exact: capabilities.zoom.max + 1 } }),
+ capabilities => ({ zoom: { exact: capabilities.zoom.min - 1 } }),
+ capabilities => ({ torch: { exact: true } }),
+];
+
+for (key in constraintGenerators) {
+ generate_tests(
+ makePromiseTest, [[
+ 'MediaStreamTrack.applyConstraints(constraints) rejects with bad' +
+ ' constraints, #' + key,
+ constraintGenerators[key]
+ ]]);
+}
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints.https.html
new file mode 100644
index 0000000000..bfbf04afdb
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints.https.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<script>
+
+const meteringModeNames = ['none', 'manual', 'single-shot', 'continuous'];
+
+// This test verifies that we can all MediaStreamTrack.applyConstraints(), with
+// a mock Mojo service implementation.
+
+image_capture_test(async (t, imageCaptureTest) => {
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ 'granted');
+
+ const constraints = { advanced : [{ whiteBalanceMode : 'single-shot',
+ exposureMode : 'manual',
+ focusMode : 'single-shot',
+
+ pointsOfInterest : [{x : 0.1, y : 0.2},
+ {x : 0.3, y : 0.4}],
+
+ exposureCompensation : 133.77,
+ exposureTime : 10000,
+ colorTemperature : 6000,
+ iso : 120.0,
+
+ brightness : 3,
+ contrast : 4,
+ saturation : 5,
+ sharpness : 6,
+ focusDistance : 7,
+
+ pan : 8,
+ tilt : 9,
+ zoom : 3.141592,
+
+ torch : true
+ }]};
+
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let videoTrack = stream.getVideoTracks()[0];
+
+ try {
+ await videoTrack.applyConstraints(constraints);
+ } catch (error) {
+ assert_unreached('applyConstraints(): ' + error.message);
+ }
+
+ const constraintsDict = constraints.advanced[0];
+ let appliedConstraints = videoTrack.getConstraints();
+ const appliedConstraintsDict = appliedConstraints.advanced[0];
+
+ // Check that |appliedConstraints| and |constraints| are equal.
+ assert_equals(constraintsDict.length, appliedConstraintsDict.length);
+ Object.keys(appliedConstraintsDict).forEach((key, value) => {
+ assert_not_equals(constraintsDict[key], undefined, 'key ' + key);
+ if (key != 'pointsOfInterest') {
+ assert_equals(constraintsDict[key], appliedConstraintsDict[key], key);
+ } else {
+ assert_point2d_array_approx_equals(constraintsDict[key],
+ appliedConstraintsDict[key], 0.01);
+ }
+ });
+
+ let theMock = imageCaptureTest.mockImageCapture();
+ assert_equals(constraintsDict.whiteBalanceMode,
+ meteringModeNames[theMock.options().whiteBalanceMode],
+ 'whiteBalanceMode');
+ assert_equals(constraintsDict.exposureMode,
+ meteringModeNames[theMock.options().exposureMode],
+ 'exposureMode');
+ assert_equals(constraintsDict.focusMode,
+ meteringModeNames[theMock.options().focusMode],
+ 'focusMode');
+
+ assert_point2d_array_approx_equals(constraintsDict.pointsOfInterest,
+ theMock.options().pointsOfInterest,
+ 0.01);
+
+ assert_equals(constraintsDict.exposureCompensation,
+ theMock.options().exposureCompensation,
+ 'exposureCompensation');
+ assert_equals(constraintsDict.exposureTime,
+ theMock.options().exposureTime,
+ 'exposureTime');
+ assert_equals(constraintsDict.colorTemperature,
+ theMock.options().colorTemperature, 'colorTemperature');
+ assert_equals(constraintsDict.iso, theMock.options().iso, 'iso');
+
+ assert_equals(constraintsDict.brightness, theMock.options().brightness,
+ 'brightness');
+ assert_equals(constraintsDict.contrast, theMock.options().contrast,
+ 'constrast');
+ assert_equals(constraintsDict.saturation, theMock.options().saturation,
+ 'saturation');
+ assert_equals(constraintsDict.sharpness, theMock.options().sharpness,
+ 'sharpness');
+ assert_equals(constraintsDict.focusDistance, theMock.options().focusDistance
+ ,'focusDistance');
+
+ assert_equals(constraintsDict.pan, theMock.options().pan, 'pan');
+ assert_equals(constraintsDict.tilt, theMock.options().tilt, 'tilt');
+ assert_equals(constraintsDict.zoom, theMock.options().zoom, 'zoom');
+
+ assert_equals(constraintsDict.torch, theMock.options().torch, 'torch');
+
+}, 'exercises MediaStreamTrack.applyConstraints(constraints)');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-clone.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-clone.https.html
new file mode 100644
index 0000000000..8945c1d9ea
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-clone.https.html
@@ -0,0 +1,371 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<script>
+
+// This test verifies that we can set some nondefault constraints, then clone a
+// MediaStreamTrack and check that the cloned constraints are the same as the
+// original, with a mock Mojo service implementation.
+image_capture_test(async (t, imageCaptureTest) => {
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ 'granted');
+
+ const constraints = { advanced : [{ whiteBalanceMode : 'single-shot',
+ exposureMode : 'manual',
+ focusMode : 'single-shot',
+
+ pointsOfInterest : [{x : 0.1, y : 0.2},
+ {x : 0.3, y : 0.4}],
+
+ exposureCompensation : 133.77,
+ exposureTime : 10000,
+ colorTemperature : 6000,
+ iso : 120.0,
+
+ brightness : 3,
+ contrast : 4,
+ saturation : 5,
+ sharpness : 6,
+ focusDistance : 7,
+
+ pan : 8,
+ tilt : 9,
+ zoom : 3.141592,
+
+ torch : true
+ }]};
+ for (const [key, value] of Object.entries(constraints.advanced[0])) {
+ constraints[key] = {exact: value};
+ }
+
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let originalVideoTrack = stream.getVideoTracks()[0];
+
+ await originalVideoTrack.applyConstraints(constraints);
+
+ let appliedConstraints = originalVideoTrack.getConstraints();
+
+ let clonedVideoTrack = originalVideoTrack.clone();
+ let appliedClonedConstraints = clonedVideoTrack.getConstraints();
+ assert_true('advanced' in appliedClonedConstraints);
+ assert_equals(appliedClonedConstraints.advanced.length, 1);
+ const appliedClonedAdvancedConstraints = appliedClonedConstraints.advanced[0];
+
+ // Check that |appliedClonedConstraints| and |appliedConstraints| are equal.
+ const appliedAdvancedConstraints = appliedConstraints.advanced[0];
+ assert_equals(appliedAdvancedConstraints.length, appliedClonedAdvancedConstraints.length);
+ Object.keys(appliedClonedAdvancedConstraints).forEach(key => {
+ assert_not_equals(appliedClonedConstraints[key], undefined, 'key ' + key);
+ assert_not_equals(appliedConstraints[key], undefined, 'key ' + key);
+ assert_not_equals(appliedConstraints[key].exact, undefined, 'key ' + key);
+ assert_not_equals(appliedAdvancedConstraints[key], undefined, 'key ' + key);
+ if (key != 'pointsOfInterest') {
+ assert_equals(appliedConstraints[key].exact, appliedClonedConstraints[key].exact, key);
+ assert_equals(appliedAdvancedConstraints[key], appliedClonedAdvancedConstraints[key], key);
+ } else {
+ assert_point2d_array_approx_equals(appliedConstraints[key].exact,
+ appliedClonedConstraints[key].exact, 0.01);
+ assert_point2d_array_approx_equals(appliedAdvancedConstraints[key],
+ appliedClonedAdvancedConstraints[key], 0.01);
+ }
+ });
+
+ assert_equals(appliedAdvancedConstraints.whiteBalanceMode,
+ appliedClonedAdvancedConstraints.whiteBalanceMode,
+ 'whiteBalanceMode');
+ assert_equals(appliedAdvancedConstraints.exposureMode,
+ appliedClonedAdvancedConstraints.exposureMode,
+ 'exposureMode');
+ assert_equals(appliedAdvancedConstraints.focusMode,
+ appliedClonedAdvancedConstraints.focusMode,
+ 'focusMode');
+
+ assert_point2d_array_approx_equals(
+ appliedAdvancedConstraints.pointsOfInterest,
+ appliedClonedAdvancedConstraints.pointsOfInterest,
+ 0.01);
+
+ assert_equals(appliedAdvancedConstraints.exposureCompensation,
+ appliedClonedAdvancedConstraints.exposureCompensation,
+ 'exposureCompensation');
+ assert_equals(appliedAdvancedConstraints.exposureTime,
+ appliedClonedAdvancedConstraints.exposureTime,
+ 'exposureTime');
+ assert_equals(appliedAdvancedConstraints.colorTemperature,
+ appliedClonedAdvancedConstraints.colorTemperature,
+ 'colorTemperature');
+ assert_equals(appliedAdvancedConstraints.iso,
+ appliedClonedAdvancedConstraints.iso,
+ 'iso');
+ assert_equals(appliedAdvancedConstraints.brightness,
+ appliedClonedAdvancedConstraints.brightness,
+ 'brightness');
+ assert_equals(appliedAdvancedConstraints.contrast,
+ appliedClonedAdvancedConstraints.contrast,
+ 'constrast');
+ assert_equals(appliedAdvancedConstraints.saturation,
+ appliedClonedAdvancedConstraints.saturation,
+ 'saturation');
+ assert_equals(appliedAdvancedConstraints.sharpness,
+ appliedClonedAdvancedConstraints.sharpness,
+ 'sharpness');
+ assert_equals(appliedAdvancedConstraints.focusDistance,
+ appliedClonedAdvancedConstraints.focusDistance,
+ 'focusDistance');
+
+ assert_equals(appliedAdvancedConstraints.pan,
+ appliedClonedAdvancedConstraints.pan,
+ 'pan');
+ assert_equals(appliedAdvancedConstraints.tilt,
+ appliedClonedAdvancedConstraints.tilt,
+ 'tilt');
+ assert_equals(appliedAdvancedConstraints.zoom,
+ appliedClonedAdvancedConstraints.zoom,
+ 'zoom');
+
+ assert_equals(appliedAdvancedConstraints.torch,
+ appliedClonedAdvancedConstraints.torch,
+ 'torch');
+}, 'checks MediaStreamTrack.clone() gets same applied constraints');
+
+// This test verifies that MediaStreamTrack ImageCapture settings are copied
+// when cloning a MediaStreamTrack.
+image_capture_test(async (t, imageCaptureTest) => {
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ 'granted');
+
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let originalVideoTrack = stream.getVideoTracks()[0];
+ let clonedVideoTrack = originalVideoTrack.clone();
+
+ let settings = originalVideoTrack.getSettings();
+ let clonedSettings = clonedVideoTrack.getSettings();
+
+ assert_equals(settings.whiteBalanceMode,
+ clonedSettings.whiteBalanceMode,
+ 'whiteBalanceMode');
+ assert_equals(settings.exposureMode,
+ clonedSettings.exposureMode,
+ 'exposureMode;');
+ assert_equals(settings.focusMode,
+ clonedSettings.focusMode,
+ 'focusMode');
+
+ assert_point2d_array_approx_equals(
+ settings.pointsOfInterest,
+ clonedSettings.pointsOfInterest,
+ 0.01);
+
+ assert_equals(settings.exposureCompensation,
+ clonedSettings.exposureCompensation,
+ 'exposureCompensation');
+ assert_equals(settings.exposureTime,
+ clonedSettings.exposureTime,
+ 'exposureTime');
+ assert_equals(settings.colorTemperature,
+ clonedSettings.colorTemperature,
+ 'colorTemperature');
+ assert_equals(settings.iso,
+ clonedSettings.iso,
+ 'iso');
+
+ assert_equals(settings.brightness,
+ clonedSettings.brightness,
+ 'brightness');
+ assert_equals(settings.contrast,
+ clonedSettings.contrast,
+ 'contrast');
+ assert_equals(settings.saturation,
+ clonedSettings.saturation,
+ 'saturation');
+ assert_equals(settings.sharpness,
+ clonedSettings.sharpness,
+ 'sharpness');
+
+ assert_equals(settings.focusDistance,
+ clonedSettings.focusDistance,
+ 'focusDistance');
+
+ assert_equals(settings.pan,
+ clonedSettings.pan,
+ 'pan');
+ assert_equals(settings.tilt,
+ clonedSettings.tilt,
+ 'tilt');
+ assert_equals(settings.zoom,
+ clonedSettings.zoom,
+ 'zoom');
+
+ assert_equals(settings.torch,
+ clonedSettings.torch,
+ 'torch');
+}, 'checks MediaStreamTrack.clone() gets same settings');
+
+// This test verifies that MediaStreamTrack ImageCapture capabilities are copied
+// when cloning a MediaStreamTrack.
+image_capture_test(async (t, imageCaptureTest) => {
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ 'granted');
+
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let originalVideoTrack = stream.getVideoTracks()[0];
+ let clonedVideoTrack = originalVideoTrack.clone();
+
+ let capabilities = originalVideoTrack.getCapabilities();
+ let clonedCapabilities = clonedVideoTrack.getCapabilities();
+
+ assert_equals(capabilities.whiteBalanceMode.length,
+ clonedCapabilities.whiteBalanceMode.length,
+ 'whiteBalanceMode length');
+ for (i = 0; i < capabilities.whiteBalanceMode.length; ++i) {
+ assert_equals(capabilities.whiteBalanceMode[i],
+ clonedCapabilities.whiteBalanceMode[i],
+ 'whiteBalanceMode');
+ }
+
+ assert_equals(capabilities.exposureMode.length,
+ clonedCapabilities.exposureMode.length,
+ 'exposureMode length');
+ for (i = 0; i < capabilities.exposureMode.length; ++i) {
+ assert_equals(capabilities.exposureMode[i],
+ clonedCapabilities.exposureMode[i],
+ 'exposureMode');
+ }
+
+ assert_equals(capabilities.focusMode.length,
+ clonedCapabilities.focusMode.length,
+ 'focusMode length');
+ for (i = 0; i < capabilities.focusMode.length; ++i) {
+ assert_equals(capabilities.focusMode[i],
+ clonedCapabilities.focusMode[i],
+ 'focusMode');
+ }
+
+ assert_equals(capabilities.exposureCompensation.max,
+ clonedCapabilities.exposureCompensation.max,
+ 'exposureCompensation max');
+ assert_equals(capabilities.exposureCompensation.min,
+ clonedCapabilities.exposureCompensation.min,
+ 'exposureCompensation min');
+ assert_equals(capabilities.exposureCompensation.step,
+ clonedCapabilities.exposureCompensation.step,
+ 'exposureCompensation step');
+
+ assert_equals(capabilities.exposureTime.max,
+ clonedCapabilities.exposureTime.max,
+ 'exposureTime max');
+ assert_equals(capabilities.exposureTime.min,
+ clonedCapabilities.exposureTime.min,
+ 'exposureTime min');
+ assert_equals(capabilities.exposureTime.step,
+ clonedCapabilities.exposureTime.step,
+ 'exposureTime step');
+
+ assert_equals(capabilities.colorTemperature.max,
+ clonedCapabilities.colorTemperature.max,
+ 'colorTemperature max');
+ assert_equals(capabilities.colorTemperature.min,
+ clonedCapabilities.colorTemperature.min,
+ 'colorTemperature min');
+ assert_equals(capabilities.colorTemperature.step,
+ clonedCapabilities.colorTemperature.step,
+ 'colorTemperature step');
+
+ assert_equals(capabilities.iso.max,
+ clonedCapabilities.iso.max,
+ 'iso max');
+ assert_equals(capabilities.iso.min,
+ clonedCapabilities.iso.min,
+ 'iso min');
+ assert_equals(capabilities.iso.step,
+ clonedCapabilities.iso.step,
+ 'iso step');
+
+ assert_equals(capabilities.brightness.max,
+ clonedCapabilities.brightness.max,
+ 'brightness max');
+ assert_equals(capabilities.brightness.min,
+ clonedCapabilities.brightness.min,
+ 'brightness min');
+ assert_equals(capabilities.brightness.step,
+ clonedCapabilities.brightness.step,
+ 'brightness step');
+
+ assert_equals(capabilities.contrast.max,
+ clonedCapabilities.contrast.max,
+ 'contrast max');
+ assert_equals(capabilities.contrast.min,
+ clonedCapabilities.contrast.min,
+ 'contrast min');
+ assert_equals(capabilities.contrast.step,
+ clonedCapabilities.contrast.step,
+ 'contrast step');
+
+ assert_equals(capabilities.saturation.max,
+ clonedCapabilities.saturation.max,
+ 'saturation max');
+ assert_equals(capabilities.saturation.min,
+ clonedCapabilities.saturation.min,
+ 'saturation min');
+ assert_equals(capabilities.saturation.step,
+ clonedCapabilities.saturation.step,
+ 'saturation step');
+
+ assert_equals(capabilities.sharpness.max,
+ clonedCapabilities.sharpness.max,
+ 'sharpness max');
+ assert_equals(capabilities.sharpness.min,
+ clonedCapabilities.sharpness.min,
+ 'sharpness min');
+ assert_equals(capabilities.sharpness.step,
+ clonedCapabilities.sharpness.step,
+ 'sharpness step');
+
+ assert_equals(capabilities.focusDistance.max,
+ clonedCapabilities.focusDistance.max,
+ 'focusDistance max');
+ assert_equals(capabilities.focusDistance.min,
+ clonedCapabilities.focusDistance.min,
+ 'focusDistance min');
+ assert_equals(capabilities.focusDistance.step,
+ clonedCapabilities.focusDistance.step,
+ 'focusDistance step');
+
+ assert_equals(capabilities.pan.max,
+ clonedCapabilities.pan.max,
+ 'pan max');
+ assert_equals(capabilities.pan.min,
+ clonedCapabilities.pan.min,
+ 'pan min');
+ assert_equals(capabilities.pan.step,
+ clonedCapabilities.pan.step,
+ 'pan step');
+
+ assert_equals(capabilities.tilt.max,
+ clonedCapabilities.tilt.max,
+ 'tilt max');
+ assert_equals(capabilities.tilt.min,
+ clonedCapabilities.tilt.min,
+ 'tilt min');
+ assert_equals(capabilities.tilt.step,
+ clonedCapabilities.tilt.step,
+ 'tilt step');
+
+ assert_equals(capabilities.zoom.max,
+ clonedCapabilities.zoom.max,
+ 'zoom max');
+ assert_equals(capabilities.zoom.min,
+ clonedCapabilities.zoom.min,
+ 'zoom min');
+ assert_equals(capabilities.zoom.step,
+ clonedCapabilities.zoom.step,
+ 'zoom step');
+
+ assert_equals(capabilities.torch,
+ clonedCapabilities.torch,
+ 'torch');
+}, 'checks MediaStreamTrack.clone() gets same capabilities');
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities-fast.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities-fast.html
new file mode 100644
index 0000000000..391dbb58f0
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities-fast.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<canvas id='canvas' width=10 height=10></canvas>
+</body>
+<script>
+
+// This test verifies that MediaStreamTrack.getCapabilities() exists and that it
+// returns something. Other tests go deeper.
+test(function() {
+ var canvas = document.getElementById('canvas');
+ var context = canvas.getContext("2d");
+ context.fillStyle = "red";
+ context.fillRect(0, 0, 10, 10);
+
+ var stream = canvas.captureStream();
+ assert_equals(stream.getAudioTracks().length, 0);
+ assert_equals(stream.getVideoTracks().length, 1);
+
+ var videoTrack = stream.getVideoTracks()[0];
+
+ assert_equals(typeof videoTrack.getCapabilities, 'function');
+
+ capabilities = videoTrack.getCapabilities();
+ assert_equals(typeof capabilities, 'object');
+}, 'MediaStreamTrack.getCapabilities()');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities.https.html
new file mode 100644
index 0000000000..6a4835a475
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities.https.html
@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<script>
+
+const meteringModeNames = ['none', 'manual', 'single-shot', 'continuous'];
+
+// This test verifies that MediaTrackCapabilities are returned upon
+// MediaStreamTrack.getCapabilities(), with a mock Mojo service implementation.
+// When PTZ permission is denied though, PTZ capabilities are not available.
+
+function makeImageCaptureTest(hasPanTiltZoomPermissionGranted) {
+ image_capture_test(async (t, imageCaptureTest) => {
+ const ptzPermission = hasPanTiltZoomPermissionGranted ? 'granted' : 'denied';
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ ptzPermission);
+
+ let mockCapabilities = imageCaptureTest.mockImageCapture().state();
+
+ // |stream| must be created _after_ |mock| is constructed to give the
+ // latter time to override the bindings.
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ assert_equals(stream.getAudioTracks().length, 0);
+ assert_equals(stream.getVideoTracks().length, 1);
+
+ let videoTrack = stream.getVideoTracks()[0];
+ assert_equals(typeof videoTrack.getCapabilities, 'function');
+
+ let capabilities = videoTrack.getCapabilities();
+ assert_equals(typeof capabilities, 'object');
+
+ assert_equals(capabilities.whiteBalanceMode.length,
+ mockCapabilities.supportedWhiteBalanceModes.length,
+ 'whiteBalanceMode');
+ for (i = 0; i < capabilities.whiteBalanceMode.length; ++i) {
+ assert_equals(
+ capabilities.whiteBalanceMode[i],
+ meteringModeNames[mockCapabilities
+ .supportedWhiteBalanceModes[i]],
+ 'whiteBalanceMode');
+ }
+
+ assert_equals(capabilities.exposureMode.length,
+ mockCapabilities.supportedExposureModes.length,
+ 'exposureMode');
+ for (i = 0; i < capabilities.exposureMode.length; ++i) {
+ assert_equals(
+ capabilities.exposureMode[i],
+ meteringModeNames[mockCapabilities.supportedExposureModes[i]],
+ 'exposureMode');
+ }
+
+ assert_equals(capabilities.focusMode.length,
+ mockCapabilities.supportedFocusModes.length,
+ 'focusMode');
+ for (i = 0; i < capabilities.focusMode.length; ++i) {
+ assert_equals(
+ capabilities.focusMode[i],
+ meteringModeNames[mockCapabilities.supportedFocusModes[i]],
+ 'focusMode');
+ }
+
+ assert_equals(capabilities.exposureCompensation.max,
+ mockCapabilities.exposureCompensation.max);
+ assert_equals(capabilities.exposureCompensation.min,
+ mockCapabilities.exposureCompensation.min);
+ assert_equals(capabilities.exposureCompensation.step,
+ mockCapabilities.exposureCompensation.step);
+
+ assert_equals(capabilities.exposureTime.max,
+ mockCapabilities.exposureTime.max);
+ assert_equals(capabilities.exposureTime.min,
+ mockCapabilities.exposureTime.min);
+ assert_equals(capabilities.exposureTime.step,
+ mockCapabilities.exposureTime.step);
+
+ assert_equals(capabilities.colorTemperature.max,
+ mockCapabilities.colorTemperature.max);
+ assert_equals(capabilities.colorTemperature.min,
+ mockCapabilities.colorTemperature.min);
+ assert_equals(capabilities.colorTemperature.step,
+ mockCapabilities.colorTemperature.step);
+
+ assert_equals(capabilities.iso.max, mockCapabilities.iso.max);
+ assert_equals(capabilities.iso.min, mockCapabilities.iso.min);
+ assert_equals(capabilities.iso.step, mockCapabilities.iso.step);
+
+ assert_equals(capabilities.brightness.max,
+ mockCapabilities.brightness.max);
+ assert_equals(capabilities.brightness.min,
+ mockCapabilities.brightness.min);
+ assert_equals(capabilities.brightness.step,
+ mockCapabilities.brightness.step);
+
+ assert_equals(capabilities.contrast.max,
+ mockCapabilities.contrast.max);
+ assert_equals(capabilities.contrast.min,
+ mockCapabilities.contrast.min);
+ assert_equals(capabilities.contrast.step,
+ mockCapabilities.contrast.step);
+
+ assert_equals(capabilities.saturation.max,
+ mockCapabilities.saturation.max);
+ assert_equals(capabilities.saturation.min,
+ mockCapabilities.saturation.min);
+ assert_equals(capabilities.saturation.step,
+ mockCapabilities.saturation.step);
+
+ assert_equals(capabilities.sharpness.max,
+ mockCapabilities.sharpness.max);
+ assert_equals(capabilities.sharpness.min,
+ mockCapabilities.sharpness.min);
+ assert_equals(capabilities.sharpness.step,
+ mockCapabilities.sharpness.step);
+
+ assert_equals(capabilities.focusDistance.max,
+ mockCapabilities.focusDistance.max);
+ assert_equals(capabilities.focusDistance.min,
+ mockCapabilities.focusDistance.min);
+ assert_equals(capabilities.focusDistance.step,
+ mockCapabilities.focusDistance.step);
+
+ if (ptzPermission === 'granted') {
+ assert_equals(capabilities.pan.max, mockCapabilities.pan.max);
+ assert_equals(capabilities.pan.min, mockCapabilities.pan.min);
+ assert_equals(capabilities.pan.step, mockCapabilities.pan.step);
+
+ assert_equals(capabilities.tilt.max, mockCapabilities.tilt.max);
+ assert_equals(capabilities.tilt.min, mockCapabilities.tilt.min);
+ assert_equals(capabilities.tilt.step, mockCapabilities.tilt.step);
+
+ assert_equals(capabilities.zoom.max, mockCapabilities.zoom.max);
+ assert_equals(capabilities.zoom.min, mockCapabilities.zoom.min);
+ assert_equals(capabilities.zoom.step, mockCapabilities.zoom.step);
+ } else if (ptzPermission === 'denied') {
+ assert_false('pan' in capabilities);
+ assert_false('tilt' in capabilities);
+ assert_false('zoom' in capabilities);
+ }
+
+ assert_equals(capabilities.torch, mockCapabilities.supportsTorch,
+ 'torch');
+ });
+}
+
+generate_tests(makeImageCaptureTest, [
+ [
+ "exercises MediaStreamTrack.getCapabilities() with PTZ permission denied",
+ false,
+ ],
+ [
+ "exercises MediaStreamTrack.getCapabilities() with PTZ permission granted",
+ true,
+ ],
+]);
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getConstraints.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getConstraints.https.html
new file mode 100644
index 0000000000..70cd2f2b07
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getConstraints.https.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<script>
+
+const constraints = { whiteBalanceMode : "single-shot",
+ exposureMode : "manual",
+ focusMode : "single-shot",
+
+ exposureCompensation : 133.77,
+ exposureTime : 10000, // in nano-seconds.
+ colorTemperature : 6000,
+ iso : 120.0,
+
+ brightness : 3,
+ contrast : 4,
+ saturation : 5,
+ sharpness : 6,
+ focusDistance : 7,
+
+ pan : 8,
+ tilt : 9,
+ zoom : 3.141592
+ // TODO: torch https://crbug.com/700607.
+ };
+
+// These tests verify that MediaStreamTrack.getConstraints() exists and that,
+// returns the constraints passed beforehand with applyConstraints.
+function makePromiseTest(constraint) {
+ image_capture_test(async function(t) {
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ 'granted');
+
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let videoTrack = stream.getVideoTracks()[0];
+
+ let constraintsIn = {advanced : [ constraint ]};
+ await videoTrack.applyConstraints(constraintsIn);
+ assert_object_equals(videoTrack.getConstraints(), constraintsIn, "constraints");
+
+ // Clear constraints by sending an empty constraint set.
+ await videoTrack.applyConstraints({});
+ assert_object_equals(videoTrack.getConstraints(), {}, "constraints");
+ });
+};
+
+// Send each line of |constraints| in turn and then the whole dictionary.
+for (key in constraints) {
+ let one_constraint = {};
+ one_constraint[key] = constraints[key];
+ generate_tests(
+ makePromiseTest,
+ [[ 'MediaStreamTrack.getConstraints(), key: ' + key, one_constraint ]]);
+}
+
+generate_tests(makePromiseTest, [
+ ["MediaStreamTrack.getConstraints(), complete ", constraints],
+]);
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings-fast.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings-fast.html
new file mode 100644
index 0000000000..91c308622c
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings-fast.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<canvas id='canvas' width=10 height=10></canvas>
+</body>
+<script>
+
+// This test verifies that MediaStreamTrack.getSettings() exists and that it
+// returns something. Other tests go deeper.
+test(function() {
+ var canvas = document.getElementById('canvas');
+ var context = canvas.getContext("2d");
+ context.fillStyle = "red";
+ context.fillRect(0, 0, 10, 10);
+
+ var stream = canvas.captureStream();
+ assert_equals(stream.getAudioTracks().length, 0);
+ assert_equals(stream.getVideoTracks().length, 1);
+
+ var videoTrack = stream.getVideoTracks()[0];
+
+ assert_equals(typeof videoTrack.getSettings, 'function');
+
+ settings = videoTrack.getSettings();
+ assert_equals(typeof settings, 'object');
+}, 'MediaStreamTrack.getSettings()');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings.https.html
new file mode 100644
index 0000000000..bd8a1ea100
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings.https.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<script>
+
+const meteringModeNames = ['none', 'manual', 'single-shot', 'continuous'];
+
+// This test verifies that the settings defined in the mock Mojo service
+// implementation are the same as those returned by the corresponding
+// MediaStreamTrack.getSettings(), except for PTZ settings when PTZ
+// permission is denied.
+
+function makeImageCaptureTest(hasPanTiltZoomPermissionGranted) {
+ image_capture_test(async (t, imageCaptureTest) => {
+ const ptzPermission = hasPanTiltZoomPermissionGranted ? 'granted' : 'denied';
+ await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+ ptzPermission);
+
+ let mockSettings = imageCaptureTest.mockImageCapture().state();
+
+ // |stream| must be created _after_ |mock| is constructed to give the
+ // latter time to override the bindings.
+ let stream = await navigator.mediaDevices.getUserMedia({video: true});
+ let videoTrack = stream.getVideoTracks()[0];
+
+ // |videoTrack|s settings retrieval, just like the actual capture, is a
+ // process kicked right after creation, we introduce a small delay to
+ // allow for those to be collected.
+ await new Promise(resolve => step_timeout(resolve, 100));
+
+ let settings = videoTrack.getSettings();
+ assert_equals(typeof settings, 'object');
+
+ assert_equals(settings.whiteBalanceMode,
+ meteringModeNames[mockSettings.currentWhiteBalanceMode],
+ 'whiteBalanceMode');
+ assert_equals(settings.exposureMode,
+ meteringModeNames[mockSettings.currentExposureMode],
+ 'exposureMode;');
+ assert_equals(settings.focusMode,
+ meteringModeNames[mockSettings.currentFocusMode],
+ 'focusMode');
+
+ assert_point2d_array_approx_equals(
+ settings.pointsOfInterest, mockSettings.pointsOfInterest, 0.01);
+
+ assert_equals(settings.exposureCompensation,
+ mockSettings.exposureCompensation.current);
+ assert_equals(settings.exposureTime,
+ mockSettings.exposureTime.current);
+ assert_equals(settings.colorTemperature,
+ mockSettings.colorTemperature.current);
+ assert_equals(settings.iso, mockSettings.iso.current);
+
+ assert_equals(settings.brightness, mockSettings.brightness.current);
+ assert_equals(settings.contrast, mockSettings.contrast.current);
+ assert_equals(settings.saturation, mockSettings.saturation.current);
+ assert_equals(settings.sharpness, mockSettings.sharpness.current);
+
+ assert_equals(settings.focusDistance, mockSettings.focusDistance.current);
+
+ if (ptzPermission === 'granted') {
+ assert_equals(settings.pan, mockSettings.pan.current);
+ assert_equals(settings.tilt, mockSettings.tilt.current);
+ assert_equals(settings.zoom, mockSettings.zoom.current);
+ } else if (ptzPermission === 'denied') {
+ assert_false('pan' in settings);
+ assert_false('tilt' in settings);
+ assert_false('zoom' in settings);
+ }
+
+ assert_equals(settings.torch, mockSettings.torch, 'torch');
+ });
+}
+
+generate_tests(makeImageCaptureTest, [
+ [
+ "exercises MediaStreamTrack.getSettings() with PTZ permission denied",
+ false,
+ ],
+ [
+ "exercises MediaStreamTrack.getSettings() with PTZ permission granted",
+ true,
+ ],
+]);
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/detached-HTMLCanvasElement.html b/testing/web-platform/tests/mediacapture-image/detached-HTMLCanvasElement.html
new file mode 100644
index 0000000000..9ccc51f7b1
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/detached-HTMLCanvasElement.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+
+async_test(t => {
+ let iframe = document.createElement('iframe');
+ let html = "<canvas id='canvas' width=10 height=10></canvas>";
+ iframe.srcdoc = html;
+ iframe.onload = t.step_func_done(() => {
+ // This detaches the frame while retaining a reference to an
+ // HTMLCanvasElement from it.
+ let canvas = iframe.contentWindow.document.getElementById('canvas');
+ document.body.removeChild(iframe);
+
+ // Creation of the ImageCapture object (as part of the MediaStreamTrack)
+ // should be safe even if the frame is detached.
+ canvas.captureStream();
+ });
+
+ document.body.appendChild(iframe);
+}, 'MediaStreamTrack can be obtained from a detached frame');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/mediacapture-image/getPhotoCapabilities.html b/testing/web-platform/tests/mediacapture-image/getPhotoCapabilities.html
new file mode 100644
index 0000000000..8b6ab891ff
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/getPhotoCapabilities.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<body>
+<canvas id='canvas' width=10 height=10></canvas>
+</body>
+<script>
+
+// This test verifies that ImageCapture can get PhotoCapabilities(), with a mock
+// Mojo interface implementation.
+
+image_capture_test(async (t, imageCaptureTest) => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+ let stream = canvas.captureStream();
+
+ let mockCapabilities = imageCaptureTest.mockImageCapture().state();
+ let capturer = new ImageCapture(stream.getVideoTracks()[0]);
+ assert_equals(typeof capturer.getPhotoCapabilities, 'function');
+
+ let capabilities = await capturer.getPhotoCapabilities();
+
+ assert_equals(capabilities.redEyeReduction, 'controllable',
+ 'redEyeReduction');
+
+ assert_equals(capabilities.imageHeight.max, mockCapabilities.height.max);
+ assert_equals(capabilities.imageHeight.min, mockCapabilities.height.min);
+ assert_equals(capabilities.imageHeight.step, mockCapabilities.height.step);
+
+ assert_equals(capabilities.imageWidth.max, mockCapabilities.width.max);
+ assert_equals(capabilities.imageWidth.min, mockCapabilities.width.min);
+ assert_equals(capabilities.imageWidth.step, mockCapabilities.width.step);
+
+ assert_array_equals(capabilities.fillLightMode, [ 'auto', 'flash' ],
+ 'fillLightMode');
+
+}, 'exercises ImageCapture.getPhotoCapabilities()');
+
+promise_test(t => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+ let stream = canvas.captureStream();
+ let videoTrack = stream.getVideoTracks()[0];
+ videoTrack.stop();
+
+ let capturer = new ImageCapture(videoTrack);
+ assert_equals(videoTrack.readyState, 'ended');
+
+ return promise_rejects_dom(t, 'InvalidStateError', capturer.getPhotoCapabilities())
+
+}, 'getPhotoCapabilities() of an ended Track should throw "InvalidStateError"');
+
+async_test(t => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+ let stream = canvas.captureStream();
+ let videoTrack = stream.getVideoTracks()[0];
+
+ let capturer = new ImageCapture(videoTrack);
+ capturer.getPhotoCapabilities()
+ .then(t.step_func_done(() => assert_unreached('should throw "OperationError"')))
+ .catch(t.step_func_done(e => assert_equals(e.name, 'OperationError')))
+ videoTrack.stop();
+}, 'throw "OperationError" when the MediaStreamTrack is stopped while getting photo capabilities');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/getPhotoSettings.html b/testing/web-platform/tests/mediacapture-image/getPhotoSettings.html
new file mode 100644
index 0000000000..04a7a5395f
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/getPhotoSettings.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<body>
+<canvas id='canvas' width=10 height=10></canvas>
+</body>
+<script>
+
+const fillLightModeNames = ['off', 'auto', 'flash'];
+
+// This test verifies that ImageCapture can call getPhotoSettings(), with a
+// mock Mojo interface implementation.
+image_capture_test(async (t, imageCaptureTest) => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+ let stream = canvas.captureStream();
+
+ let mockState = imageCaptureTest.mockImageCapture().state();
+ let capturer = new ImageCapture(stream.getVideoTracks()[0]);
+ let settings = await capturer.getPhotoSettings();
+ assert_equals(settings.imageWidth, mockState.width.current, 'width');
+ assert_equals(settings.imageHeight, mockState.height.current, 'height');
+ // TODO(mcasas): check the remaining two entries https://crbug.com/732521.
+
+}, 'exercises ImageCapture.getPhotoSettings()');
+
+promise_test(t => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+ let stream = canvas.captureStream();
+ let videoTrack = stream.getVideoTracks()[0];
+ videoTrack.stop();
+
+ let capturer = new ImageCapture(videoTrack);
+ assert_equals(videoTrack.readyState, 'ended');
+
+ return promise_rejects_dom(t, 'InvalidStateError', capturer.getPhotoSettings())
+
+}, 'getPhotoSettings() of an ended Track should throw "InvalidStateError"');
+
+async_test(t => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+ let stream = canvas.captureStream();
+ let videoTrack = stream.getVideoTracks()[0];
+
+ let capturer = new ImageCapture(videoTrack);
+ capturer.getPhotoSettings()
+ .then(t.step_func_done(() => assert_unreached('should throw "OperationError"')))
+ .catch(t.step_func_done(e => assert_equals(e.name, 'OperationError')))
+ videoTrack.stop();
+}, 'throw "OperationError" when the MediaStreamTrack is stopped while getting photo settings');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/getusermedia.https.html b/testing/web-platform/tests/mediacapture-image/getusermedia.https.html
new file mode 100644
index 0000000000..033501cf64
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/getusermedia.https.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>getUserMedia</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+
+ [
+ { video: { pan: { min: 1 } } },
+ { video: { pan: { max: 1 } } },
+ { video: { pan: { exact: 1 } } },
+ { video: { tilt: { min: 1 } } },
+ { video: { tilt: { max: 1 } } },
+ { video: { tilt: { exact: 1 } } },
+ { video: { zoom: { min: 1 } } },
+ { video: { zoom: { max: 1 } } },
+ { video: { zoom: { exact: 1 } } }
+ ].forEach(constraints =>
+ promise_test(t => {
+ const promise = navigator.mediaDevices.getUserMedia(constraints);
+ return promise_rejects_js(t, TypeError, promise);
+ }, `getUserMedia(${JSON.stringify(constraints)}) must fail with TypeError`)
+ );
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/idlharness.window.js b/testing/web-platform/tests/mediacapture-image/idlharness.window.js
new file mode 100644
index 0000000000..2977138647
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/idlharness.window.js
@@ -0,0 +1,25 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: timeout=long
+
+// https://w3c.github.io/mediacapture-image/
+
+'use strict';
+
+idl_test(
+ ['image-capture'],
+ ['mediacapture-streams', 'html', 'dom'],
+ idl_array => {
+ idl_array.add_objects({
+ ImageCapture : ['capture'],
+ });
+
+ const canvas = document.createElement('canvas');
+ document.body.appendChild(canvas);
+ const context = canvas.getContext("2d");
+ context.fillStyle = "red";
+ context.fillRect(0, 0, 10, 10);
+ const track = canvas.captureStream().getVideoTracks()[0];
+ self.capture = new ImageCapture(track);
+ }
+);
diff --git a/testing/web-platform/tests/mediacapture-image/resources/imagecapture-helpers.js b/testing/web-platform/tests/mediacapture-image/resources/imagecapture-helpers.js
new file mode 100644
index 0000000000..8f142cff41
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/resources/imagecapture-helpers.js
@@ -0,0 +1,55 @@
+'use strict';
+
+// These tests rely on the User Agent providing an implementation of
+// platform image capture backends.
+//
+// In Chromium-based browsers this implementation is provided by a polyfill
+// in order to reduce the amount of test-only code shipped to users. To enable
+// these tests the browser must be run with these options:
+//
+// --enable-blink-features=MojoJS,MojoJSTest
+
+async function loadChromiumResources() {
+ await import('/resources/chromium/mock-imagecapture.js');
+}
+
+async function initialize_image_capture_tests() {
+ if (typeof ImageCaptureTest === 'undefined') {
+ const script = document.createElement('script');
+ script.src = '/resources/test-only-api.js';
+ script.async = false;
+ const p = new Promise((resolve, reject) => {
+ script.onload = () => { resolve(); };
+ script.onerror = e => { reject(e); };
+ })
+ document.head.appendChild(script);
+ await p;
+
+ if (isChromiumBased) {
+ await loadChromiumResources();
+ }
+ }
+ assert_implements(ImageCaptureTest, 'ImageCaptureTest is unavailable');
+ let imageCaptureTest = new ImageCaptureTest();
+ await imageCaptureTest.initialize();
+ return imageCaptureTest;
+}
+
+function image_capture_test(func, name, properties) {
+ promise_test(async (t) => {
+ let imageCaptureTest = await initialize_image_capture_tests();
+ try {
+ await func(t, imageCaptureTest);
+ } finally {
+ await imageCaptureTest.reset();
+ };
+ }, name, properties);
+}
+
+function assert_point2d_array_approx_equals(actual, expected, epsilon) {
+ assert_equals(actual.length, expected.length, 'length');
+ for (var i = 0; i < actual.length; ++i) {
+ assert_approx_equals(actual[i].x, expected[i].x, epsilon, 'x');
+ assert_approx_equals(actual[i].y, expected[i].y, epsilon, 'y');
+ }
+}
diff --git a/testing/web-platform/tests/mediacapture-image/takePhoto-reject.html b/testing/web-platform/tests/mediacapture-image/takePhoto-reject.html
new file mode 100644
index 0000000000..fff36e1a3b
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/takePhoto-reject.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<body>
+<canvas id='canvas' width=10 height=10></canvas>
+</body>
+<script>
+
+let canvas = document.getElementById('canvas');
+let context = canvas.getContext('2d');
+context.fillStyle = 'red';
+context.fillRect(0, 0, 10, 10);
+
+// This test verifies that ImageCapture.takePhoto() rejects if any passed
+// option is unsupported or outside its allowed range.
+function makePromiseTest(getOption) {
+ image_capture_test(async (t, imageCaptureTest) => {
+ imageCaptureTest.mockImageCapture().state().redEyeReduction = 0;
+
+ let stream = canvas.captureStream();
+ let capturer = new ImageCapture(stream.getVideoTracks()[0]);
+ await capturer.getPhotoCapabilities();
+ const options = getOption(imageCaptureTest.mockImageCapture().state());
+
+ try {
+ await capturer.takePhoto(options);
+ assert_unreached('expected takePhoto to reject');
+ } catch (error) {
+ assert_equals(error.name, 'NotSupportedError');
+ }
+ });
+}
+
+const optionsGenerators = [
+ capabilities => ({ redEyeReduction: true }),
+ capabilities => ({ imageHeight: capabilities.height.max + 1 }),
+ capabilities => ({ imageHeight: capabilities.height.min - 1 }),
+ capabilities => ({ imageWidth: capabilities.width.max + 1 }),
+ capabilities => ({ imageWidth: capabilities.width.min - 1 }),
+ capabilities => ({ fillLightMode: 'off' }),
+];
+
+for (key in optionsGenerators) {
+ generate_tests(
+ makePromiseTest,
+ [[ 'ImageCapture.takePhoto(options) rejects with bad options, #' + key,
+ optionsGenerators[key] ]]);
+}
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/takePhoto-with-PhotoSettings.html b/testing/web-platform/tests/mediacapture-image/takePhoto-with-PhotoSettings.html
new file mode 100644
index 0000000000..2946e782d0
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/takePhoto-with-PhotoSettings.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<body>
+<canvas id='canvas' width=10 height=10></canvas>
+</body>
+<script>
+
+const fillLightModeNames = ['off', 'auto', 'flash'];
+
+// This test verifies that ImageCapture can call takePhoto with a PhotoSettings
+// argument, with a mock Mojo interface implementation.
+
+image_capture_test(async (t, imageCaptureTest) => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+ let stream = canvas.captureStream();
+
+ const optionsDict = { imageWidth : 1080,
+ imageHeight : 100,
+ redEyeReduction : true,
+ fillLightMode : 'flash'
+ };
+
+ let capturer = new ImageCapture(stream.getVideoTracks()[0]);
+ let blob = await capturer.takePhoto(optionsDict);
+
+ // JS Blob is almost-opaque, can only check |type| and |size|.
+ assert_equals(blob.type, 'image/cat');
+ assert_equals(blob.size, 2);
+
+
+ assert_equals(true, imageCaptureTest.mockImageCapture().options().hasWidth,
+ 'hasWidth');
+ assert_equals(optionsDict.imageWidth,
+ imageCaptureTest.mockImageCapture().options().width,'width');
+ assert_equals(true, imageCaptureTest.mockImageCapture().options().hasHeight,
+ 'hasHeight');
+ assert_equals(optionsDict.imageHeight,
+ imageCaptureTest.mockImageCapture().options().height,
+ 'height');
+
+ // Depending on how mojo boolean packing in integers is arranged, this can
+ // be a number instead of a boolean, compare directly.
+ // TODO(mcasas): Revert to assert_equals() when yzshen@ has sorted it out.
+ assert_true(
+ optionsDict.redEyeReduction == imageCaptureTest.mockImageCapture().
+ options().redEyeReduction, 'redEyeReduction');
+
+ assert_equals(true,
+ imageCaptureTest.mockImageCapture().options().hasFillLightMode,
+ 'hasFillLightMode');
+ assert_equals(optionsDict.fillLightMode,
+ fillLightModeNames[
+ imageCaptureTest.mockImageCapture().options().fillLightMode],
+ 'fillLightMode');
+
+}, 'exercises ImageCapture.takePhoto(PhotoSettings dictionary)');
+
+</script>
diff --git a/testing/web-platform/tests/mediacapture-image/takePhoto-without-PhotoCapabilities.https.window.js b/testing/web-platform/tests/mediacapture-image/takePhoto-without-PhotoCapabilities.https.window.js
new file mode 100644
index 0000000000..96eb253ccd
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/takePhoto-without-PhotoCapabilities.https.window.js
@@ -0,0 +1,6 @@
+promise_test(async t => {
+ const track = new MediaStreamTrackGenerator('video');
+ const capturer = new ImageCapture(track);
+ const settings = await capturer.getPhotoSettings();
+ await promise_rejects_dom(t, 'UnknownError', capturer.takePhoto(settings));
+}, 'exercise takePhoto() on a track without PhotoCapabilities');
diff --git a/testing/web-platform/tests/mediacapture-image/takePhoto.html b/testing/web-platform/tests/mediacapture-image/takePhoto.html
new file mode 100644
index 0000000000..44586406c4
--- /dev/null
+++ b/testing/web-platform/tests/mediacapture-image/takePhoto.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script>
+<body>
+<canvas id='canvas' width=10 height=10></canvas>
+</body>
+<script>
+
+// This test verifies that ImageCapture can takePhoto()s, with a mock Mojo
+// interface implementation.
+
+image_capture_test(async t => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+ let stream = canvas.captureStream();
+
+ let capturer = new ImageCapture(stream.getVideoTracks()[0]);
+ let blob = await capturer.takePhoto();
+
+ // JS Blob is almost-opaque, can only check |type| and |size|.
+ assert_equals(blob.type, 'image/cat');
+ assert_equals(blob.size, 2);
+
+}, 'exercises ImageCapture.takePhoto()');
+
+image_capture_test(async t => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+ let stream = canvas.captureStream();
+
+ let capturer = new ImageCapture(stream.getVideoTracks()[0]);
+ let blob = await capturer.takePhoto(null);
+
+ // JS Blob is almost-opaque, can only check |type| and |size|.
+ assert_equals(blob.type, 'image/cat');
+ assert_equals(blob.size, 2);
+
+}, 'exercises ImageCapture.takePhoto(null)');
+
+promise_test(t => {
+ let canvas = document.getElementById('canvas');
+ let context = canvas.getContext('2d');
+ context.fillStyle = 'red';
+ context.fillRect(0, 0, 10, 10);
+ let stream = canvas.captureStream();
+ let videoTrack = stream.getVideoTracks()[0];
+ videoTrack.stop();
+
+ let capturer = new ImageCapture(videoTrack);
+ assert_equals(videoTrack.readyState, 'ended');
+
+ return promise_rejects_dom(t, 'InvalidStateError', capturer.takePhoto())
+
+}, 'takePhoto() of an ended Track should throw "InvalidStateError"');
+
+</script>