Bug 1505878 [wpt PR 13984] - Add report-only mode to Feature Policy, a=testonly
authorIan Clelland <iclelland@chromium.org>
Mon, 19 Nov 2018 18:45:31 +0000
changeset 506764 91431840cca377524058fdd4715986ae537e67a8
parent 506763 1d062b58e9b47169735a582d0b3f853655a282ec
child 506765 5a02af0869cc9e135b62550ee653ca79d2af96e5
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1505878, 13984, 904878, 1178811, 608004
milestone65.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 1505878 [wpt PR 13984] - Add report-only mode to Feature Policy, a=testonly Automatic update from web-platform-testsAdd report-only mode to Feature Policy This change adds a "report-only" policy to each security context, which is local to that context (not replicated across processes) and is used to determine whether a report should be sent even if a feature is enabled, when the feature is used. Reports are now augmented with a "disposition" field, which is either "enforce", if the feature usage was actually blocked, or "report", if it was not. Feature policy directives are placed in the report-only policy if the feature name is suffixed with "-report-only", otherwise, they affect the regular (enforcing) policy. Explainer at https://github.com/WICG/feature-policy/blob/master/reporting.md Existing tests are updated, and new tests for report-only mode are added to ensure that reports are sent even when the feature is used successfully. Bug: 904878 Change-Id: I27bc42729c5ab5560160f3d993431e606a8a3a47 Reviewed-on: https://chromium-review.googlesource.com/c/1178811 Commit-Queue: Ian Clelland <iclelland@chromium.org> Reviewed-by: Ken Buchanan <kenrb@chromium.org> Reviewed-by: Dmitry Gozman <dgozman@chromium.org> Cr-Commit-Position: refs/heads/master@{#608004} -- wpt-commits: b39f5e0d0e0872cdd9b7592f0b9ff0b5c11bcef3 wpt-pr: 13984
testing/web-platform/tests/feature-policy/reporting/camera-report-only.https.html
testing/web-platform/tests/feature-policy/reporting/camera-report-only.https.html.headers
testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html
testing/web-platform/tests/feature-policy/reporting/document-write-report-only.html
testing/web-platform/tests/feature-policy/reporting/document-write-report-only.html.headers
testing/web-platform/tests/feature-policy/reporting/document-write-reporting.html
testing/web-platform/tests/feature-policy/reporting/encrypted-media-report-only.https.html
testing/web-platform/tests/feature-policy/reporting/encrypted-media-report-only.https.html.headers
testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html
testing/web-platform/tests/feature-policy/reporting/fullscreen-report-only.html
testing/web-platform/tests/feature-policy/reporting/fullscreen-report-only.html.headers
testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html
testing/web-platform/tests/feature-policy/reporting/generic-sensor-report-only.https.html
testing/web-platform/tests/feature-policy/reporting/generic-sensor-report-only.https.html.headers
testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html
testing/web-platform/tests/feature-policy/reporting/geolocation-report-only.https.html
testing/web-platform/tests/feature-policy/reporting/geolocation-report-only.https.html.headers
testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html
testing/web-platform/tests/feature-policy/reporting/microphone-report-only.https.html
testing/web-platform/tests/feature-policy/reporting/microphone-report-only.https.html.headers
testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html
testing/web-platform/tests/feature-policy/reporting/midi-report-only.html
testing/web-platform/tests/feature-policy/reporting/midi-report-only.html.headers
testing/web-platform/tests/feature-policy/reporting/midi-reporting.html
testing/web-platform/tests/feature-policy/reporting/payment-report-only.https.html
testing/web-platform/tests/feature-policy/reporting/payment-report-only.https.html.headers
testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html
testing/web-platform/tests/feature-policy/reporting/picture-in-picture-report-only.html
testing/web-platform/tests/feature-policy/reporting/picture-in-picture-report-only.html.headers
testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html
testing/web-platform/tests/feature-policy/reporting/sync-xhr-report-only.html
testing/web-platform/tests/feature-policy/reporting/sync-xhr-report-only.html.headers
testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html
testing/web-platform/tests/feature-policy/reporting/usb-report-only.https.html
testing/web-platform/tests/feature-policy/reporting/usb-report-only.https.html.headers
testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html
testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html
testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html.headers
testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html
testing/web-platform/tests/feature-policy/reporting/xr-report-only.https.html
testing/web-platform/tests/feature-policy/reporting/xr-report-only.https.html.headers
testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/camera-report-only.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <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>
+  </head>
+  <body>
+    <script>
+var check_report_format = ([reports, observer]) => {
+  let report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "camera");
+  assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  await test_driver.bless('Activate document for user media');
+  await navigator.mediaDevices.getUserMedia({video: true});
+  check_report_format(await report);
+}, "Camera report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/camera-report-only.https.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: camera-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html
+++ b/testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html
@@ -8,16 +8,17 @@
     <script>
 var t = async_test("Camera Report Format");
 
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "camera");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 new ReportingObserver(t.step_func_done(check_report_format),
                       {types: ['feature-policy-violation']}).observe();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/document-write-report-only.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src='/resources/testharness.js'></script>
+    <script src='/resources/testharnessreport.js'></script>
+  </head>
+  <body>
+    <script>
+var check_report_format = ([reports, observer]) => {
+  let report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "document-write");
+  assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  document.write("This should be written into the document");
+  check_report_format(await report);
+}, "Document-write report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/document-write-report-only.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: document-write-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/document-write-reporting.html
+++ b/testing/web-platform/tests/feature-policy/reporting/document-write-reporting.html
@@ -8,16 +8,17 @@
     <script>
 var t = async_test("Document-write Report Format");
 
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "document-write");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 new ReportingObserver(t.step_func_done(check_report_format),
                       {types: ['feature-policy-violation']}).observe();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/encrypted-media-report-only.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src='/resources/testharness.js'></script>
+    <script src='/resources/testharnessreport.js'></script>
+  </head>
+  <body>
+    <script>
+var check_report_format = ([reports, observer]) => {
+  let report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "encrypted-media");
+  assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  await navigator.requestMediaKeySystemAccess("org.w3.clearkey",
+      [{
+        initDataTypes: ["webm"],
+        videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}],
+      }]);
+  check_report_format(await report);
+}, "Encrypted Media report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/encrypted-media-report-only.https.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: encrypted-media-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html
+++ b/testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html
@@ -6,16 +6,17 @@
   </head>
   <body>
     <script>
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.feature, "encrypted-media");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.message, "string");
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
 };
 
 promise_test(async t => {
   const report = new Promise(resolve => {
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/fullscreen-report-only.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <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>
+  </head>
+  <body>
+    <div id='fs'></div>
+    <script>
+var check_report_format = ([reports, observer]) => {
+  let report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "fullscreen");
+  assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  await test_driver.bless('Activate document for fullscreen');
+  await document.getElementById('fs').requestFullscreen();
+  check_report_format(await report);
+  document.exitFullscreen();
+}, "Fullscreen report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/fullscreen-report-only.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: fullscreen-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html
+++ b/testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html
@@ -7,16 +7,17 @@
   <body>
     <div id='fs'></div>
     <script>
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "fullscreen");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 async_test(t => {
   new ReportingObserver(t.step_func_done(check_report_format),
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/generic-sensor-report-only.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src='/resources/testharness.js'></script>
+    <script src='/resources/testharnessreport.js'></script>
+  </head>
+  <body>
+    <script>
+var sensor_features_verified = {
+  "accelerometer": false,
+  "ambient-light-sensor": false,
+  "magnetometer": false,
+  "gyroscope": false
+};
+
+var check_report_format = function(reports, observer) {
+  // Check each report in this batch. This observer callback may be called
+  // multiple times before all reports have been processed.
+  for (const report of reports) {
+
+    // Validate that the reported feature is one of the sensor features, and that
+    // we have not seen a report for this feature before.
+    assert_true(sensor_features_verified.hasOwnProperty(report.body.feature));
+    assert_false(sensor_features_verified[report.body.feature]);
+
+    // Validate the remainder of the report
+    assert_equals(report.type, "feature-policy");
+    assert_equals(report.url, document.location.href);
+    assert_equals(report.body.disposition, "report");
+    assert_equals(report.body.sourceFile, document.location.href);
+    assert_equals(typeof report.body.message, "string");
+    assert_equals(typeof report.body.lineNumber, "number");
+    assert_equals(typeof report.body.columnNumber, "number");
+
+    sensor_features_verified[report.body.feature] = true;
+  }
+
+  // Test is only done when reports for all features have been seen
+  for (let result of Object.values(sensor_features_verified)) {
+    if (!result)
+      return;
+  }
+  this.done();
+};
+
+async_test(t => {
+  new ReportingObserver(t.step_func(check_report_format),
+                        {types: ['feature-policy']}).observe();
+  new Accelerometer();
+  new AmbientLightSensor();
+  new Gyroscope();
+  new Magnetometer();
+}, "Generic Sensor report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/generic-sensor-report-only.https.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: ambient-light-sensor-report-only 'none'; accelerometer-report-only 'none'; gyroscope-report-only 'none'; magnetometer-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html
+++ b/testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html
@@ -21,16 +21,17 @@ var check_report_format = function(repor
     // Validate that the reported feature is one of the sensor features, and that
     // we have not seen a report for this feature before.
     assert_true(sensor_features_verified.hasOwnProperty(report.body.feature));
     assert_false(sensor_features_verified[report.body.feature]);
 
     // Validate the remainder of the report
     assert_equals(report.type, "feature-policy");
     assert_equals(report.url, document.location.href);
+    assert_equals(report.body.disposition, "enforce");
     assert_equals(report.body.sourceFile, document.location.href);
     assert_equals(typeof report.body.message, "string");
     assert_equals(typeof report.body.lineNumber, "number");
     assert_equals(typeof report.body.columnNumber, "number");
 
     sensor_features_verified[report.body.feature] = true;
   }
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/geolocation-report-only.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src='/resources/testharness.js'></script>
+    <script src='/resources/testharnessreport.js'></script>
+  </head>
+  <body>
+    <script>
+var check_report_format = ([reports, observer]) => {
+  let report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "geolocation");
+  assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  try {
+    await new Promise((resolve, reject) => {
+      navigator.geolocation.getCurrentPosition(resolve, reject);
+    });
+    check_report_format(await report);
+  } catch (err) {
+    // In case the getCurrentPosition call was rejected due to user permissions,
+    // the report should be generated anyway. Wait for it and check the format
+    // before failing this test.
+    check_report_format(await report);
+    throw err;
+  }
+}, "Geolocation report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/geolocation-report-only.https.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: geolocation-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html
+++ b/testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html
@@ -8,16 +8,19 @@
     <script>
 var t = async_test("Geolocation Report Format");
 
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "geolocation");
+  assert_equals(report.body.disposition, "enforce");
+  assert_equals(report.body.sourceFile, document.location.href);
+  assert_equals(typeof report.body.message, "string");
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 new ReportingObserver(t.step_func_done(check_report_format),
                       {types: ['feature-policy-violation']}).observe();
 
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/microphone-report-only.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <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>
+  </head>
+  <body>
+    <script>
+var check_report_format = ([reports, observer]) => {
+  let report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "microphone");
+  assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  await test_driver.bless('Activate document for user media');
+  await navigator.mediaDevices.getUserMedia({audio: true});
+  check_report_format(await report);
+}, "Microphone report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/microphone-report-only.https.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: microphone-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html
+++ b/testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html
@@ -8,16 +8,17 @@
     <script>
 var t = async_test("Microphone Report Format");
 
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "microphone");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 new ReportingObserver(t.step_func_done(check_report_format),
                       {types: ['feature-policy-violation']}).observe();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/midi-report-only.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src='/resources/testharness.js'></script>
+    <script src='/resources/testharnessreport.js'></script>
+  </head>
+  <body>
+    <script>
+var check_report_format = ([reports, observer]) => {
+  let report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "midi");
+  assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  try {
+    await navigator.requestMIDIAccess();
+    check_report_format(await report);
+  } catch (err) {
+    // In case the requestMIDIAccess call was rejected due to user permissions,
+    // the report should be generated anyway. Wait for it and check the format
+    // before failing this test.
+    check_report_format(await report);
+    throw err;
+  }
+}, "MIDI report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/midi-report-only.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: midi-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/midi-reporting.html
+++ b/testing/web-platform/tests/feature-policy/reporting/midi-reporting.html
@@ -6,16 +6,17 @@
   </head>
   <body>
     <script>
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "midi");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 promise_test(async (t) => {
   const report = new Promise(resolve => {
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/payment-report-only.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src='/resources/testharness.js'></script>
+    <script src='/resources/testharnessreport.js'></script>
+  </head>
+  <body>
+    <script>
+var check_report_format = ([reports, observer]) => {
+  let report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "payment");
+  assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  try {
+    const request = new PaymentRequest(
+      [{ supportedMethods: 'basic-card' }],
+      { total: { label: 'Total', amount: { currency: 'USD', value: 0 }}},
+      {});
+    await request.show()
+    check_report_format(await report);
+  } catch (err) {
+    // In case the show call was rejected, the report should be generated
+    // anyway. Wait for it and check the format before failing this test.
+    check_report_format(await report);
+    throw err;
+  }
+}, "PaymentRequest report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/payment-report-only.https.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: payment-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html
+++ b/testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html
@@ -8,16 +8,17 @@
     <script>
 var t = async_test("PaymentRequest Report Format");
 
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "payment");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 new ReportingObserver(t.step_func_done(check_report_format),
                       {types: ['feature-policy-violation']}).observe();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/picture-in-picture-report-only.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src='/common/media.js'></script>
+    <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='../resources/picture-in-picture.js'></script>
+  </head>
+  <body>
+    <script>
+const check_report_format = ([reports, observer]) => {
+  const report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "picture-in-picture");
+  assert_equals(report.body.disposition, "report");
+};
+
+const loadVideo = () => new Promise(resolve => {
+    const video = document.createElement('video');
+    video.src = getVideoURI('/media/movie_5');
+    video.addEventListener('loadedmetadata', () => {
+      resolve(video);
+    }, { once: true });
+});
+
+promise_pip_test(async (t) => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  const videoElement = await loadVideo();
+  await test_driver.bless('picture-in-picture');
+  await videoElement.requestPictureInPicture();
+  check_report_format(await report);
+}, "Picture-in-Picture report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/picture-in-picture-report-only.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: picture-in-picture-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html
+++ b/testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html
@@ -10,16 +10,17 @@
   </head>
   <body>
     <script>
 const check_report_format = (reports, observer) => {
   const report = reports[0];
   assert_equals(report.type, "feature-policy");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.feature, "picture-in-picture");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.message, "string");
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
 };
 
 const loadVideo = () => new Promise(resolve => {
     const video = document.createElement('video');
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/sync-xhr-report-only.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src='/resources/testharness.js'></script>
+    <script src='/resources/testharnessreport.js'></script>
+  </head>
+  <body>
+    <script>
+const check_report_format = ([reports, observer]) => {
+  const report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "sync-xhr");
+  assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  const xhr = new XMLHttpRequest();
+  xhr.open("GET", document.location.href, false);
+  xhr.send();
+  check_report_format(await report);
+}, "Sync-xhr report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/sync-xhr-report-only.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: sync-xhr-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html
+++ b/testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html
@@ -8,16 +8,17 @@
     <script>
 var t = async_test("Sync-xhr Report Format");
 
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "sync-xhr");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 new ReportingObserver(t.step_func_done(check_report_format),
                       {types: ['feature-policy-violation']}).observe();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/usb-report-only.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <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>
+  </head>
+  <body>
+    <div id='fs'></div>
+    <script>
+var check_report_format = ([reports, observer]) => {
+  let report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.body.feature, "usb");
+  assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  await test_driver.bless('Activate document for USB');
+  await navigator.usb.getDevices();
+  check_report_format(await report);
+}, "USB report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/usb-report-only.https.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: usb-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html
+++ b/testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html
@@ -8,16 +8,17 @@
     <script>
 var t = async_test("USB Report Format");
 
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "usb");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 new ReportingObserver(t.step_func_done(check_report_format),
                       {types: ['feature-policy-violation']}).observe();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src='/resources/testharness.js'></script>
+    <script src='/resources/testharnessreport.js'></script>
+  </head>
+  <body>
+    <script>
+const check_report_format = ([reports, observer]) => {
+  const report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.url, document.location.href);
+  assert_equals(report.body.feature, "vr");
+  assert_equals(report.body.disposition, "report");
+  assert_equals(report.body.sourceFile, document.location.href);
+  assert_equals(typeof report.body.message, "string");
+  assert_equals(typeof report.body.lineNumber, "number");
+  assert_equals(typeof report.body.columnNumber, "number");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  await navigator.getVRDisplays();
+  check_report_format(await report);
+}, "VR report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: vr-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html
+++ b/testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html
@@ -6,16 +6,17 @@
   </head>
   <body>
     <script>
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "vr");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 promise_test(async (t) => {
   const report = new Promise(resolve => {
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/xr-report-only.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src='/resources/testharness.js'></script>
+    <script src='/resources/testharnessreport.js'></script>
+  </head>
+  <body>
+    <script>
+const check_report_format = ([reports, observer]) => {
+  const report = reports[0];
+  assert_equals(report.type, "feature-policy");
+  assert_equals(report.url, document.location.href);
+  assert_equals(report.body.feature, "vr");
+  assert_equals(report.body.disposition, "report");
+  assert_equals(report.body.sourceFile, document.location.href);
+  assert_equals(typeof report.body.message, "string");
+  assert_equals(typeof report.body.lineNumber, "number");
+  assert_equals(typeof report.body.columnNumber, "number");
+};
+
+promise_test(async t => {
+  const report = new Promise(resolve => {
+    new ReportingObserver((reports, observer) => resolve([reports, observer]),
+                          {types: ['feature-policy']}).observe();
+  });
+  try {
+    await navigator.xr.requestDevice();
+  } catch (err) {
+    // If no XR devices are available, requestDevice() will throw NotFoundError,
+    // but the report should be generated anyway.
+    assert_equals(err.name, 'NotFoundError');
+  }
+  check_report_format(await report);
+}, "XR report only mode");
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/xr-report-only.https.html.headers
@@ -0,0 +1,1 @@
+Feature-Policy: vr-report-only 'none'
--- a/testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html
+++ b/testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html
@@ -6,16 +6,17 @@
   </head>
   <body>
     <script>
 var check_report_format = (reports, observer) => {
   let report = reports[0];
   assert_equals(report.type, "feature-policy-violation");
   assert_equals(report.url, document.location.href);
   assert_equals(report.body.featureId, "vr");
+  assert_equals(report.body.disposition, "enforce");
   assert_equals(report.body.sourceFile, document.location.href);
   assert_equals(typeof report.body.lineNumber, "number");
   assert_equals(typeof report.body.columnNumber, "number");
   assert_equals(report.body.disposition, "enforce");
 };
 
 promise_test(async (t) => {
   const report = new Promise(resolve => {