[PTZ] Expose image capture pan & tilt only if PTZ permission is granted

This CL makes sure the pan/tilt capabilities/constraints/settings
are available only if PTZ permission is granted to the website when the
the MediaCapturePanTilt feature is enabled.
If the MediaCapturePanTilt feature is disabled, the pan/tilt (not zoom)
capabilities/constraints/settings are not available due to the presence
of [RuntimeEnabled=MediaCapturePanTilt] attributes in WebIDLs while the
zoom capabilities/constraints/settings is still available regardless of
the PTZ permission.

An upcoming CL regarding PTZ support in getUserMedia will address a
potential race condition that may happen if PTZ permission status is not
retrieved when getting pan/tilt capabilities/constraints/settings.

Manual test: https://ptz.glitch.me/
Explainer: https://github.com/w3c/mediacapture-image/blob/master/ptz-explainer.md

Change-Id: Ieaefb0e239dd6921cdd4de5441afb7cd4050bf5b
Bug: 934063
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2151567
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Mounir Lamouri <mlamouri@chromium.org>
Reviewed-by: Andy Paicu <andypaicu@chromium.org>
Reviewed-by: Rijubrata Bhaumik <rijubrata.bhaumik@intel.com>
Reviewed-by: Guido Urdaneta <guidou@chromium.org>
Commit-Queue: François Beaufort <beaufort.francois@gmail.com>
Cr-Commit-Position: refs/heads/master@{#769235}
diff --git a/lint.ignore b/lint.ignore
index 8cfef1e..ddb9f9f 100644
--- a/lint.ignore
+++ b/lint.ignore
@@ -262,7 +262,9 @@
 GENERATE_TESTS: domxpath/001.html
 GENERATE_TESTS: domxpath/002.html
 GENERATE_TESTS: mediacapture-image/MediaStreamTrack-applyConstraints-reject.html
+GENERATE_TESTS: mediacapture-image/MediaStreamTrack-getCapabilities.html
 GENERATE_TESTS: mediacapture-image/MediaStreamTrack-getConstraints-fast.html
+GENERATE_TESTS: mediacapture-image/MediaStreamTrack-getSettings.html
 GENERATE_TESTS: mediacapture-image/setOptions-reject.html
 GENERATE_TESTS: html/semantics/scripting-1/the-template-element/template-element/template-as-a-descendant.html
 GENERATE_TESTS: html/syntax/parsing/Document.getElementsByTagName-foreign-01.html
diff --git a/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.html b/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.html
index 9d985de..a1695b1 100644
--- a/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.html
+++ b/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.html
@@ -1,6 +1,8 @@
 <!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>
 <body>
 <canvas id='canvas' width=10 height=10/>
@@ -11,6 +13,9 @@
 // service implementation, are returned by MediaStreamTrack.getSettings().
 
 image_capture_test(async t => {
+  await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+        'granted', false);
+
   let canvas = document.getElementById('canvas');
   let context = canvas.getContext('2d');
   context.fillStyle = 'red';
@@ -98,6 +103,40 @@
 
   assert_equals(constraints.advanced[0].torch, settings.torch, 'torch');
 
-}, 'exercises an applyConstraints() - getSettings() cycle');
+}, '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', false);
+
+  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|'s capabilities gathering, just like the actual capture, is
+  // a process kicked off right after creation, we introduce a small delay
+  // to allow for those to be collected, since they are needed to understand
+  // which constraints are supported in applyConstraints().
+  // TODO(mcasas): this shouldn't be needed, https://crbug.com/711524.
+  await new Promise(resolve => step_timeout(resolve, 100));
+
+  const constraints = [{ pan: 8 }, { tilt: 9 }];
+  await Promise.all(constraints.map(async constraint =>
+    promise_rejects_dom(
+        t, 'NotAllowedError',
+        videoTrack.applyConstraints({ advanced: [constraint] }),
+        "applyConstraints should throw a NotAllowedError for " +
+        JSON.stringify(constraint))
+  ));
+
+}, 'exercises an applyConstraints() with PTZ permission denied');
 
 </script>
\ No newline at end of file
diff --git a/mediacapture-image/MediaStreamTrack-applyConstraints-reject.html b/mediacapture-image/MediaStreamTrack-applyConstraints-reject.html
index 2171963..45599c0 100644
--- a/mediacapture-image/MediaStreamTrack-applyConstraints-reject.html
+++ b/mediacapture-image/MediaStreamTrack-applyConstraints-reject.html
@@ -1,6 +1,8 @@
 <!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>
 <body>
 <canvas id='canvas' width=10 height=10/>
@@ -16,6 +18,9 @@
 // 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', false);
+
     imageCaptureTest.mockImageCapture().state().supportsTorch = false;
 
     let stream = canvas.captureStream();
diff --git a/mediacapture-image/MediaStreamTrack-applyConstraints.html b/mediacapture-image/MediaStreamTrack-applyConstraints.html
index da3de3e..3c5c05a 100644
--- a/mediacapture-image/MediaStreamTrack-applyConstraints.html
+++ b/mediacapture-image/MediaStreamTrack-applyConstraints.html
@@ -1,6 +1,8 @@
 <!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>
 <body>
 <canvas id='canvas' width=10 height=10/>
@@ -13,6 +15,9 @@
 // a mock Mojo service implementation.
 
 image_capture_test(async (t, imageCaptureTest) => {
+  await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+        'granted', false);
+
   let canvas = document.getElementById('canvas');
   let context = canvas.getContext('2d');
   context.fillStyle = 'red';
@@ -113,6 +118,7 @@
 
     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');
 
diff --git a/mediacapture-image/MediaStreamTrack-getCapabilities.html b/mediacapture-image/MediaStreamTrack-getCapabilities.html
index e7b196f..c569283 100644
--- a/mediacapture-image/MediaStreamTrack-getCapabilities.html
+++ b/mediacapture-image/MediaStreamTrack-getCapabilities.html
@@ -1,6 +1,8 @@
 <!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>
 <body>
 <canvas id='canvas' width=10 height=10/>
@@ -11,154 +13,174 @@
 
 // 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.
 
-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);
+function makeImageCaptureTest(hasPanTiltZoomPermissionGranted) {
+  image_capture_test(async (t, imageCaptureTest) => {
+    const ptzPermission = hasPanTiltZoomPermissionGranted ? 'granted' : 'denied';
+    await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+        ptzPermission, false);
 
-  let mockCapabilities = imageCaptureTest.mockImageCapture().state();
+    let canvas = document.getElementById('canvas');
+    let context = canvas.getContext('2d');
+    context.fillStyle = 'red';
+    context.fillRect(0, 0, 10, 10);
 
-  // |stream| must be created _after_ |mock| is constructed to give the
-  // latter time to override the bindings.
-  let stream = canvas.captureStream();
-  assert_equals(stream.getAudioTracks().length, 0);
-  assert_equals(stream.getVideoTracks().length, 1);
+    let mockCapabilities = imageCaptureTest.mockImageCapture().state();
 
-  let videoTrack = stream.getVideoTracks()[0];
-  assert_equals(typeof videoTrack.getCapabilities, 'function');
+    // |stream| must be created _after_ |mock| is constructed to give the
+    // latter time to override the bindings.
+    let stream = canvas.captureStream();
+    assert_equals(stream.getAudioTracks().length, 0);
+    assert_equals(stream.getVideoTracks().length, 1);
 
-  // |videoTrack|'s capabilities gathering, just like the actual capture, is
-  // a process kicked off right after creation, we introduce a small delay
-  // to allow for those to be collected.
-  // TODO(mcasas): this shouldn't be needed, https://crbug.com/711524.
-  await new Promise(resolve => step_timeout(resolve, 100));
+    let videoTrack = stream.getVideoTracks()[0];
+    assert_equals(typeof videoTrack.getCapabilities, 'function');
 
-  let capabilities = videoTrack.getCapabilities();
-  assert_equals(typeof capabilities, 'object');
+    // |videoTrack|'s capabilities gathering, just like the actual capture, is
+    // a process kicked off right after creation, we introduce a small delay
+    // to allow for those to be collected.
+    // TODO(mcasas): this shouldn't be needed, https://crbug.com/711524.
+    await new Promise(resolve => step_timeout(resolve, 100));
 
-  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');
-  }
+    let capabilities = videoTrack.getCapabilities();
+    assert_equals(typeof capabilities, 'object');
 
-  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.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.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.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_true(capabilities.exposureCompensation instanceof
-              MediaSettingsRange);
-  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.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_true(capabilities.exposureTime instanceof
-              MediaSettingsRange);
-  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_true(capabilities.exposureCompensation instanceof
+                MediaSettingsRange);
+    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_true(capabilities.colorTemperature instanceof
-              MediaSettingsRange);
-  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_true(capabilities.exposureTime instanceof
+                MediaSettingsRange);
+    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_true(capabilities.iso instanceof MediaSettingsRange);
-  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_true(capabilities.colorTemperature instanceof
+                MediaSettingsRange);
+    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_true(capabilities.brightness instanceof MediaSettingsRange);
-  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_true(capabilities.iso instanceof MediaSettingsRange);
+    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_true(capabilities.contrast instanceof MediaSettingsRange);
-  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_true(capabilities.brightness instanceof MediaSettingsRange);
+    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_true(capabilities.saturation instanceof MediaSettingsRange);
-  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_true(capabilities.contrast instanceof MediaSettingsRange);
+    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_true(capabilities.sharpness instanceof MediaSettingsRange);
-  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_true(capabilities.saturation instanceof MediaSettingsRange);
+    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_true(capabilities.focusDistance instanceof MediaSettingsRange);
-  assert_equals(capabilities.focusDistance.max,
-                mockCapabilities.focusDistance.max);
-  assert_equals(capabilities.focusDistance.min,
-                mockCapabilities.focusDistance.min);
-  assert_equals(capabilities.focusDistance.step,
-                mockCapabilities.focusDistance.step);
+    assert_true(capabilities.sharpness instanceof MediaSettingsRange);
+    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_true(capabilities.pan instanceof MediaSettingsRange);
-  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_true(capabilities.focusDistance instanceof MediaSettingsRange);
+    assert_equals(capabilities.focusDistance.max,
+                  mockCapabilities.focusDistance.max);
+    assert_equals(capabilities.focusDistance.min,
+                  mockCapabilities.focusDistance.min);
+    assert_equals(capabilities.focusDistance.step,
+                  mockCapabilities.focusDistance.step);
 
-  assert_true(capabilities.tilt instanceof MediaSettingsRange);
-  assert_equals(capabilities.tilt.max, mockCapabilities.tilt.max);
-  assert_equals(capabilities.tilt.min, mockCapabilities.tilt.min);
-  assert_equals(capabilities.tilt.step, mockCapabilities.tilt.step);
+    if (ptzPermission === 'granted') {
+      assert_true(capabilities.pan instanceof MediaSettingsRange);
+      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_true(capabilities.zoom instanceof MediaSettingsRange);
-  assert_equals(capabilities.zoom.max, mockCapabilities.zoom.max);
-  assert_equals(capabilities.zoom.min, mockCapabilities.zoom.min);
-  assert_equals(capabilities.zoom.step, mockCapabilities.zoom.step);
+      assert_true(capabilities.tilt instanceof MediaSettingsRange);
+      assert_equals(capabilities.tilt.max, mockCapabilities.tilt.max);
+      assert_equals(capabilities.tilt.min, mockCapabilities.tilt.min);
+      assert_equals(capabilities.tilt.step, mockCapabilities.tilt.step);
+    } else if (ptzPermission === 'denied') {
+      assert_false('pan' in capabilities);
+      assert_false('tilt' in capabilities);
+    }
+    assert_true(capabilities.zoom instanceof MediaSettingsRange);
+    assert_equals(capabilities.zoom.max, mockCapabilities.zoom.max);
+    assert_equals(capabilities.zoom.min, mockCapabilities.zoom.min);
+    assert_equals(capabilities.zoom.step, mockCapabilities.zoom.step);
 
-  assert_equals(capabilities.torch, mockCapabilities.supportsTorch,
-                'torch');
+    assert_equals(capabilities.torch, mockCapabilities.supportsTorch,
+                  'torch');
+  });
+}
 
-}, 'exercises MediaStreamTrack.getCapabilities()');
-
+generate_tests(makeImageCaptureTest, [
+  [
+    "exercises MediaStreamTrack.getCapabilities() with PTZ permission denied",
+    false,
+  ],
+  [
+    "exercises MediaStreamTrack.getCapabilities() with PTZ permission granted",
+    true,
+  ],
+]);
 </script>
diff --git a/mediacapture-image/MediaStreamTrack-getSettings.html b/mediacapture-image/MediaStreamTrack-getSettings.html
index a1a864c..8fc2c82 100644
--- a/mediacapture-image/MediaStreamTrack-getSettings.html
+++ b/mediacapture-image/MediaStreamTrack-getSettings.html
@@ -1,6 +1,8 @@
 <!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>
 <body>
 <canvas id='canvas' width=10 height=10/>
@@ -11,63 +13,84 @@
 
 // This test verifies that the settings defined in the mock Mojo service
 // implementation are the same as those returned by the corresponding
-// MediaStreamTrack.getSettings().
+// MediaStreamTrack.getSettings(), except for PTZ settings when PTZ
+// permission is denied.
 
-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);
+function makeImageCaptureTest(hasPanTiltZoomPermissionGranted) {
+  image_capture_test(async (t, imageCaptureTest) => {
+    const ptzPermission = hasPanTiltZoomPermissionGranted ? 'granted' : 'denied';
+    await test_driver.set_permission({name: 'camera', panTiltZoom: true},
+        ptzPermission, false);
 
-  let mockSettings = imageCaptureTest.mockImageCapture().state();
+    let canvas = document.getElementById('canvas');
+    let context = canvas.getContext('2d');
+    context.fillStyle = 'red';
+    context.fillRect(0, 0, 10, 10);
 
-  // |stream| must be created _after_ |mock| is constructed to give the
-  // latter time to override the bindings.
-  let stream = canvas.captureStream();
-  let videoTrack = stream.getVideoTracks()[0];
+    let mockSettings = imageCaptureTest.mockImageCapture().state();
 
-  // |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));
+    // |stream| must be created _after_ |mock| is constructed to give the
+    // latter time to override the bindings.
+    let stream = canvas.captureStream();
+    let videoTrack = stream.getVideoTracks()[0];
 
-  let settings = videoTrack.getSettings();
-  assert_equals(typeof settings, 'object');
+    // |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));
 
-  assert_equals(settings.whiteBalanceMode,
-                meteringModeNames[mockSettings.currentWhiteBalanceMode],
-                'whiteBalanceMode');
-  assert_equals(settings.exposureMode,
-                meteringModeNames[mockSettings.currentExposureMode],
-                'exposureMode;');
-  assert_equals(settings.focusMode,
-                meteringModeNames[mockSettings.currentFocusMode],
-                'focusMode');
+    let settings = videoTrack.getSettings();
+    assert_equals(typeof settings, 'object');
 
-  assert_point2d_array_approx_equals(
-      settings.pointsOfInterest, mockSettings.pointsOfInterest, 0.01);
+    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_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_point2d_array_approx_equals(
+        settings.pointsOfInterest, mockSettings.pointsOfInterest, 0.01);
 
-  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.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.focusDistance, mockSettings.focusDistance.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.pan, mockSettings.pan.current);
-  assert_equals(settings.tilt, mockSettings.tilt.current);
-  assert_equals(settings.zoom, mockSettings.zoom.current);
+    assert_equals(settings.focusDistance, mockSettings.focusDistance.current);
 
-  assert_equals(settings.torch, mockSettings.torch, 'torch');
+    if (ptzPermission === 'granted') {
+      assert_equals(settings.pan, mockSettings.pan.current);
+      assert_equals(settings.tilt, mockSettings.tilt.current);
+    } else if (ptzPermission === 'denied') {
+      assert_false('pan' in settings);
+      assert_false('tilt' in settings);
+    }
+    assert_equals(settings.zoom, mockSettings.zoom.current);
 
-}, 'exercises MediaStreamTrack.getSettings()');
+    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>