Bug 1517681 - Fix MediaStream-default-feature-policy.https.html to respect getUserMedia result guarantees. r=baku
authorJan-Ivar Bruaroey <jib@mozilla.com>
Wed, 16 Jan 2019 12:52:11 +0000
changeset 514064 16709683a80e03a024abd1c0e90f1b223cbf9fb0
parent 514063 530ea0f2b9ab059ec00889d9cee67b37e4fd9a58
child 514065 7c8c157560bbad6e93fd3bdf609e394c8bac7cea
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1517681
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1517681 - Fix MediaStream-default-feature-policy.https.html to respect getUserMedia result guarantees. r=baku Differential Revision: https://phabricator.services.mozilla.com/D15701
testing/web-platform/meta/mediacapture-streams/MediaStream-default-feature-policy.https.html.ini
testing/web-platform/tests/feature-policy/resources/featurepolicy.js
testing/web-platform/tests/mediacapture-streams/MediaStream-default-feature-policy.https.html
--- a/testing/web-platform/meta/mediacapture-streams/MediaStream-default-feature-policy.https.html.ini
+++ b/testing/web-platform/meta/mediacapture-streams/MediaStream-default-feature-policy.https.html.ini
@@ -1,21 +1,32 @@
 [MediaStream-default-feature-policy.https.sub.html]
   [Default "microphone" feature policy ["self"\] disallows cross-origin iframes.]
     expected: FAIL
 
   [Default "camera" feature policy ["self"\] disallows cross-origin iframes.]
     expected: FAIL
 
-  [Default "camera; microphone" feature policy ["self"\] disallows cross-origin iframes.]
+  [Default "camera;microphone" feature policy ["self"\] disallows cross-origin iframes.]
+    expected: FAIL
+
+  [Feature policy "microphone" can be enabled in cross-origin iframes using "allow" attribute.]
+    expected: FAIL
+
+  [Feature policy "camera" can be enabled in cross-origin iframes using "allow" attribute.]
     expected: FAIL
 
 
 [MediaStream-default-feature-policy.https.html]
   [Default "microphone" feature policy ["self"\] disallows cross-origin iframes.]
     expected: FAIL
 
   [Default "camera" feature policy ["self"\] disallows cross-origin iframes.]
     expected: FAIL
 
-  [Default "camera; microphone" feature policy ["self"\] disallows cross-origin iframes.]
+  [Default "camera;microphone" feature policy ["self"\] disallows cross-origin iframes.]
     expected: FAIL
 
+  [Feature policy "microphone" can be enabled in cross-origin iframes using "allow" attribute.]
+    expected: FAIL
+
+  [Feature policy "camera" can be enabled in cross-origin iframes using "allow" attribute.]
+    expected: FAIL
--- a/testing/web-platform/tests/feature-policy/resources/featurepolicy.js
+++ b/testing/web-platform/tests/feature-policy/resources/featurepolicy.js
@@ -77,17 +77,17 @@ function test_feature_availability_with_
   };
   test_feature_availability(null, test, src, test_result, allow_attribute);
 }
 
 // If this page is intended to test the named feature (according to the URL),
 // tests the feature availability and posts the result back to the parent.
 // Otherwise, does nothing.
 function test_feature_in_iframe(feature_name, feature_promise_factory) {
-  if (location.hash.includes(feature_name)) {
+  if (location.hash.endsWith(`#${feature_name}`)) {
     feature_promise_factory().then(
         () => window.parent.postMessage('#OK', '*'),
         (e) => window.parent.postMessage('#' + e.name, '*'));
   }
 }
 
 // Returns true if the URL for this page indicates that it is embedded in an
 // iframe.
--- a/testing/web-platform/tests/mediacapture-streams/MediaStream-default-feature-policy.https.html
+++ b/testing/web-platform/tests/mediacapture-streams/MediaStream-default-feature-policy.https.html
@@ -2,61 +2,78 @@
 <body>
   <script src=/resources/testharness.js></script>
   <script src=/resources/testharnessreport.js></script>
   <script src=/common/get-host-info.sub.js></script>
   <script src=/feature-policy/resources/featurepolicy.js></script>
   <script>
   'use strict';
 
-  // The promise_factory must return a promise that runs the feature and
-  // resolves if feature usage is successful, otherwise rejects. Using
-  // getUserMedia is successful if at least one mic/camera is returned when
-  // mic/camera has been explicitly allowed by feature policy.
-  function promise_factory(allowed_features) {
-    return new Promise((resolve, reject) => {
-      navigator.mediaDevices.getUserMedia({video: true, audio: true}).then(
-          function(stream) {
-            // If microphone is allowed, there should be at least one microphone
-            // in the result. If camera is allowed, there should be at least one
-            // camera in the result.
-            if ((allowed_features.includes('microphone') &&
-                 stream.getAudioTracks().length == 0) ||
-                (allowed_features.includes('camera') &&
-                 stream.getVideoTracks().length == 0)) {
-                reject('Feature policy allowed feature but devices not ' +
-                    'present.');
-            } else {
-              // Otherwise the result is expected.
-              resolve();
-            }
-          },
-          function(error) { reject(error); });
-    });
-  };
+  async function gUM({audio, video}) {
+    let stream;
+    try {
+      stream = await navigator.mediaDevices.getUserMedia({audio, video});
+      // getUserMedia must guarantee the number of tracks requested or fail.
+      if ((audio && stream.getAudioTracks().length == 0) ||
+          (video && stream.getVideoTracks().length == 0)) {
+        throw {name: `All requested devices must be present with ` +
+                     `audio ${audio} and video ${video}, or fail`};
+      }
+    } finally {
+      if (stream) {
+        stream.getTracks().forEach(track => track.stop());
+      }
+    }
+  }
 
-  var cross_domain = get_host_info().HTTPS_REMOTE_ORIGIN;
+  async function must_disallow_gUM({audio, video}) {
+    try {
+      await gUM({audio, video});
+    } catch (e) {
+      if (e.name == 'NotAllowedError') {
+        return;
+      }
+      throw e;
+    }
+    throw {name: `audio ${audio} and video ${video} constraints must not be ` +
+                 `allowed.`};
+  }
+
+  const cross_domain = get_host_info().HTTPS_REMOTE_ORIGIN;
   run_all_fp_tests_allow_self(
-      cross_domain,
-      'microphone',
-      'NotAllowedError',
-      function() {
-        return promise_factory('microphone');
-      });
-
-  run_all_fp_tests_allow_self(
-      cross_domain,
-      'camera',
-      'NotAllowedError',
-      function() {
-        return promise_factory('camera');
-      });
+    cross_domain,
+    'microphone',
+    'NotAllowedError',
+    async () => {
+      await gUM({audio: true});
+      if (window.location.href.includes(cross_domain)) {
+        await must_disallow_gUM({video: true});
+        await must_disallow_gUM({audio: true, video: true});
+      }
+    }
+  );
 
   run_all_fp_tests_allow_self(
     cross_domain,
-    'camera; microphone',
+    'camera',
     'NotAllowedError',
-    function() {
-      return promise_factory('camera; microphone');
-    });
+    async () => {
+      await gUM({video: true});
+      if (window.location.href.includes(cross_domain)) {
+        await must_disallow_gUM({audio: true});
+        await must_disallow_gUM({audio: true, video: true});
+      }
+    }
+  );
+
+  run_all_fp_tests_allow_self(
+    cross_domain,
+    'camera;microphone',
+    'NotAllowedError',
+    async () => {
+      await gUM({audio: true, video: true});
+      await gUM({audio: true});
+      await gUM({video: true});
+    }
+  );
   </script>
 </body>