Add 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}
diff --git a/content/browser/frame_host/render_frame_host_feature_policy_unittest.cc b/content/browser/frame_host/render_frame_host_feature_policy_unittest.cc
index b730cee3..5510718 100644
--- a/content/browser/frame_host/render_frame_host_feature_policy_unittest.cc
+++ b/content/browser/frame_host/render_frame_host_feature_policy_unittest.cc
@@ -84,6 +84,7 @@
blink::ParsedFeaturePolicy result(1);
result[0].feature = feature;
result[0].matches_all_origins = false;
+ result[0].disposition = blink::mojom::FeaturePolicyDisposition::kEnforce;
for (const std::string& origin : origins)
result[0].origins.push_back(url::Origin::Create(GURL(origin)));
return result;
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index d73589c..a44d629 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -2820,7 +2820,8 @@
return;
// Rebuild the feature policy for this frame.
ResetFeaturePolicy();
- feature_policy_->SetHeaderPolicy(parsed_header);
+ feature_policy_->SetHeaderPolicy(*DirectivesWithDisposition(
+ blink::mojom::FeaturePolicyDisposition::kEnforce, parsed_header));
// Update the feature policy and sandbox flags in the frame tree. This will
// send any updates to proxies if necessary.
@@ -5437,7 +5438,10 @@
blink::ParsedFeaturePolicy container_policy =
frame_tree_node()->effective_frame_policy().container_policy;
feature_policy_ = blink::FeaturePolicy::CreateFromParentPolicy(
- parent_policy, container_policy, last_committed_origin_);
+ parent_policy,
+ *DirectivesWithDisposition(
+ blink::mojom::FeaturePolicyDisposition::kEnforce, container_policy),
+ last_committed_origin_);
}
void RenderFrameHostImpl::CreateAudioInputStreamFactory(
diff --git a/content/browser/net/reporting_service_proxy.cc b/content/browser/net/reporting_service_proxy.cc
index d207fa4..f661d32 100644
--- a/content/browser/net/reporting_service_proxy.cc
+++ b/content/browser/net/reporting_service_proxy.cc
@@ -108,12 +108,14 @@
void QueueFeaturePolicyViolationReport(
const GURL& url,
const std::string& policy,
+ const std::string& disposition,
const std::string& message,
const base::Optional<std::string>& source_file,
int line_number,
int column_number) override {
auto body = std::make_unique<base::DictionaryValue>();
body->SetString("policy", policy);
+ body->SetString("disposition", disposition);
body->SetString("message", message);
if (source_file)
body->SetString("sourceFile", *source_file);
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index eafffaa..92eae27 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -479,6 +479,7 @@
blink::ParsedFeaturePolicy result(1);
result[0].feature = feature;
result[0].matches_all_origins = false;
+ result[0].disposition = blink::mojom::FeaturePolicyDisposition::kEnforce;
DCHECK(!origins.empty());
for (const GURL& origin : origins)
result[0].origins.push_back(url::Origin::Create(origin));
@@ -492,6 +493,7 @@
blink::ParsedFeaturePolicy result(1);
result[0].feature = feature;
result[0].matches_all_origins = true;
+ result[0].disposition = blink::mojom::FeaturePolicyDisposition::kEnforce;
return result;
}
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index b23fcca1..8984848f 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -153,6 +153,8 @@
IPC_ENUM_TRAITS_MIN_MAX_VALUE(content::NavigationDownloadPolicy,
content::NavigationDownloadPolicy::kAllow,
content::NavigationDownloadPolicy::kMaxValue)
+IPC_ENUM_TRAITS_MAX_VALUE(blink::mojom::FeaturePolicyDisposition,
+ blink::mojom::FeaturePolicyDisposition::kMaxValue)
IPC_STRUCT_TRAITS_BEGIN(blink::WebFloatSize)
IPC_STRUCT_TRAITS_MEMBER(width)
@@ -534,6 +536,7 @@
IPC_STRUCT_TRAITS_MEMBER(feature)
IPC_STRUCT_TRAITS_MEMBER(matches_all_origins)
IPC_STRUCT_TRAITS_MEMBER(matches_opaque_src)
+ IPC_STRUCT_TRAITS_MEMBER(disposition)
IPC_STRUCT_TRAITS_MEMBER(origins)
IPC_STRUCT_TRAITS_END()
diff --git a/content/test/test_render_frame_host.cc b/content/test/test_render_frame_host.cc
index c5628b1..e75fbfd 100644
--- a/content/test/test_render_frame_host.cc
+++ b/content/test/test_render_frame_host.cc
@@ -272,6 +272,7 @@
blink::ParsedFeaturePolicy header(1);
header[0].feature = feature;
header[0].matches_all_origins = false;
+ header[0].disposition = blink::mojom::FeaturePolicyDisposition::kEnforce;
header[0].origins = whitelist;
DidSetFramePolicyHeaders(blink::WebSandboxFlags::kNone, header);
}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/camera-report-only.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/camera-report-only.https.html
new file mode 100644
index 0000000..2648868
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/camera-report-only.https.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/camera-report-only.https.html.headers
new file mode 100644
index 0000000..46b8481
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/camera-report-only.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: camera-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/camera-reporting.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/camera-reporting.https.html
index 14b2ed1d..1d08700d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/camera-reporting.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/camera-reporting.https.html
@@ -13,6 +13,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "camera");
+ 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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/document-write-report-only.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/document-write-report-only.html
new file mode 100644
index 0000000..ab0bb827
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/document-write-report-only.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/document-write-report-only.html.headers
new file mode 100644
index 0000000..63e43f1d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/document-write-report-only.html.headers
@@ -0,0 +1 @@
+Feature-Policy: document-write-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/document-write-reporting.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/document-write-reporting.html
index cb08b8d..d113f2e 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/document-write-reporting.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/document-write-reporting.html
@@ -13,6 +13,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "document-write");
+ 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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/encrypted-media-report-only.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/encrypted-media-report-only.https.html
new file mode 100644
index 0000000..20e44b2f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/encrypted-media-report-only.https.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/encrypted-media-report-only.https.html.headers
new file mode 100644
index 0000000..a2c8fbb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/encrypted-media-report-only.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: encrypted-media-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/encrypted-media-reporting.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/encrypted-media-reporting.https.html
index d309d53..c3b6393 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/encrypted-media-reporting.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/encrypted-media-reporting.https.html
@@ -11,6 +11,7 @@
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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/fullscreen-report-only.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/fullscreen-report-only.html
new file mode 100644
index 0000000..a6b3d5ad
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/fullscreen-report-only.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/fullscreen-report-only.html.headers
new file mode 100644
index 0000000..33defa88
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/fullscreen-report-only.html.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/fullscreen-reporting.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/fullscreen-reporting.html
index 83d97c9..9a7fb87 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/fullscreen-reporting.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/fullscreen-reporting.html
@@ -12,6 +12,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "fullscreen");
+ 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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/generic-sensor-report-only.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/generic-sensor-report-only.https.html
new file mode 100644
index 0000000..deb6ade
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/generic-sensor-report-only.https.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/generic-sensor-report-only.https.html.headers
new file mode 100644
index 0000000..26605eb0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/generic-sensor-report-only.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: ambient-light-sensor-report-only 'none'; accelerometer-report-only 'none'; gyroscope-report-only 'none'; magnetometer-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/generic-sensor-reporting.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/generic-sensor-reporting.https.html
index c60e3e8..517c7f6 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/generic-sensor-reporting.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/generic-sensor-reporting.https.html
@@ -26,6 +26,7 @@
// 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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-report-only.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-report-only.https-expected.txt
new file mode 100644
index 0000000..9322061
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-report-only.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Geolocation report only mode promise_test: Unhandled rejection with value: object "[object PositionError]"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-report-only.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-report-only.https.html
new file mode 100644
index 0000000..cf2a75b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-report-only.https.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-report-only.https.html.headers
new file mode 100644
index 0000000..fc985900
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-report-only.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: geolocation-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-reporting.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-reporting.https.html
index 22e2585..ce06902 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-reporting.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/geolocation-reporting.https.html
@@ -13,6 +13,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/microphone-report-only.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/microphone-report-only.https.html
new file mode 100644
index 0000000..2d7b4d96
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/microphone-report-only.https.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/microphone-report-only.https.html.headers
new file mode 100644
index 0000000..7673d05
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/microphone-report-only.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: microphone-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/microphone-reporting.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/microphone-reporting.https.html
index 7347a23..4a0e0b5 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/microphone-reporting.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/microphone-reporting.https.html
@@ -13,6 +13,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "microphone");
+ 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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-report-only-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-report-only-expected.txt
new file mode 100644
index 0000000..bba8e2a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-report-only-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL MIDI report only mode promise_test: Unhandled rejection with value: object "SecurityError: An attempt was made to break through the security policy of the user agent."
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-report-only.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-report-only.html
new file mode 100644
index 0000000..e466ce0d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-report-only.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-report-only.html.headers
new file mode 100644
index 0000000..3c6a2d4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-report-only.html.headers
@@ -0,0 +1 @@
+Feature-Policy: midi-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-reporting.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-reporting.html
index 8303b7a..fc6d8b1a 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-reporting.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/midi-reporting.html
@@ -11,6 +11,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "midi");
+ 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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-report-only.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-report-only.https-expected.txt
new file mode 100644
index 0000000..19f3d28
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-report-only.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL PaymentRequest report only mode promise_test: Unhandled rejection with value: object "UnknownError: Request failed"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-report-only.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-report-only.https.html
new file mode 100644
index 0000000..6a7678b5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-report-only.https.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-report-only.https.html.headers
new file mode 100644
index 0000000..6411478e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-report-only.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: payment-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-reporting.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-reporting.https.html
index 03eaebe..2c1189d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-reporting.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/payment-reporting.https.html
@@ -13,6 +13,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "payment");
+ 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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/picture-in-picture-report-only.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/picture-in-picture-report-only.html
new file mode 100644
index 0000000..157670f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/picture-in-picture-report-only.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/picture-in-picture-report-only.html.headers
new file mode 100644
index 0000000..0df90a3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/picture-in-picture-report-only.html.headers
@@ -0,0 +1 @@
+Feature-Policy: picture-in-picture-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/picture-in-picture-reporting.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/picture-in-picture-reporting.html
index e3cbf10..f15f47c 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/picture-in-picture-reporting.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/picture-in-picture-reporting.html
@@ -15,6 +15,7 @@
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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/sync-xhr-report-only.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/sync-xhr-report-only.html
new file mode 100644
index 0000000..f841f63
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/sync-xhr-report-only.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/sync-xhr-report-only.html.headers
new file mode 100644
index 0000000..79a82cf
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/sync-xhr-report-only.html.headers
@@ -0,0 +1 @@
+Feature-Policy: sync-xhr-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/sync-xhr-reporting.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/sync-xhr-reporting.html
index 2c76390..f16e335 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/sync-xhr-reporting.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/sync-xhr-reporting.html
@@ -13,6 +13,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "sync-xhr");
+ 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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/usb-report-only.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/usb-report-only.https.html
new file mode 100644
index 0000000..e44c6c5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/usb-report-only.https.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/usb-report-only.https.html.headers
new file mode 100644
index 0000000..bd2e3c6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/usb-report-only.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: usb-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/usb-reporting.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/usb-reporting.https.html
index f90c602e..bc4bffd 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/usb-reporting.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/usb-reporting.https.html
@@ -13,6 +13,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "usb");
+ 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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/vr-report-only.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/vr-report-only.https.html
new file mode 100644
index 0000000..91016d3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/vr-report-only.https.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/vr-report-only.https.html.headers
new file mode 100644
index 0000000..b54cad2a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/vr-report-only.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: vr-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/vr-reporting.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/vr-reporting.https.html
index 12cae05..9278519 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/vr-reporting.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/vr-reporting.https.html
@@ -11,6 +11,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "vr");
+ 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");
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/xr-report-only.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/xr-report-only.https.html
new file mode 100644
index 0000000..5d4fb06
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/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>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/xr-report-only.https.html.headers b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/xr-report-only.https.html.headers
new file mode 100644
index 0000000..b54cad2a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/xr-report-only.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: vr-report-only 'none'
diff --git a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/xr-reporting.https.html b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/xr-reporting.https.html
index a7a1222..0c793d5b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/xr-reporting.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/feature-policy/reporting/xr-reporting.https.html
@@ -11,6 +11,7 @@
assert_equals(report.type, "feature-policy");
assert_equals(report.url, document.location.href);
assert_equals(report.body.feature, "vr");
+ 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");
diff --git a/third_party/blink/common/feature_policy/feature_policy.cc b/third_party/blink/common/feature_policy/feature_policy.cc
index d0a8054..1237911 100644
--- a/third_party/blink/common/feature_policy/feature_policy.cc
+++ b/third_party/blink/common/feature_policy/feature_policy.cc
@@ -34,10 +34,12 @@
mojom::FeaturePolicyFeature feature,
bool matches_all_origins,
bool matches_opaque_src,
+ mojom::FeaturePolicyDisposition disposition,
std::vector<url::Origin> origins)
: feature(feature),
matches_all_origins(matches_all_origins),
matches_opaque_src(matches_opaque_src),
+ disposition(disposition),
origins(origins) {}
ParsedFeaturePolicyDeclaration::ParsedFeaturePolicyDeclaration(
@@ -56,8 +58,22 @@
// but-not-identical allowlists, or eliminate those comparisons by maintaining
// the allowlists in a normalized form.
// https://crbug.com/710324
- return std::tie(lhs.feature, lhs.matches_all_origins, lhs.origins) ==
- std::tie(rhs.feature, rhs.matches_all_origins, rhs.origins);
+ return std::tie(lhs.feature, lhs.matches_all_origins, lhs.origins,
+ lhs.disposition) == std::tie(rhs.feature,
+ rhs.matches_all_origins,
+ rhs.origins, rhs.disposition);
+}
+
+std::unique_ptr<ParsedFeaturePolicy> DirectivesWithDisposition(
+ mojom::FeaturePolicyDisposition disposition,
+ const ParsedFeaturePolicy& policy) {
+ std::unique_ptr<ParsedFeaturePolicy> filtered_policy =
+ std::make_unique<ParsedFeaturePolicy>();
+ for (const auto& directive : policy) {
+ if (directive.disposition == disposition)
+ filtered_policy->push_back(directive);
+ }
+ return filtered_policy;
}
FeaturePolicy::Allowlist::Allowlist() : matches_all_origins_(false) {}
diff --git a/third_party/blink/common/feature_policy/feature_policy_mojom_traits.cc b/third_party/blink/common/feature_policy/feature_policy_mojom_traits.cc
index 468be4c..d96a6b5 100644
--- a/third_party/blink/common/feature_policy/feature_policy_mojom_traits.cc
+++ b/third_party/blink/common/feature_policy/feature_policy_mojom_traits.cc
@@ -13,6 +13,7 @@
Read(blink::mojom::ParsedFeaturePolicyDeclarationDataView in,
blink::ParsedFeaturePolicyDeclaration* out) {
out->matches_all_origins = in.matches_all_origins();
+ out->disposition = in.disposition();
return in.ReadOrigins(&out->origins) && in.ReadFeature(&out->feature);
}
diff --git a/third_party/blink/common/feature_policy/feature_policy_mojom_traits.h b/third_party/blink/common/feature_policy/feature_policy_mojom_traits.h
index 8f8bfb9..c0fe8ee 100644
--- a/third_party/blink/common/feature_policy/feature_policy_mojom_traits.h
+++ b/third_party/blink/common/feature_policy/feature_policy_mojom_traits.h
@@ -83,6 +83,10 @@
const blink::ParsedFeaturePolicyDeclaration& policy) {
return policy.matches_all_origins;
}
+ static blink::mojom::FeaturePolicyDisposition disposition(
+ const blink::ParsedFeaturePolicyDeclaration& policy) {
+ return policy.disposition;
+ }
static const std::vector<url::Origin>& origins(
const blink::ParsedFeaturePolicyDeclaration& policy) {
return policy.origins;
diff --git a/third_party/blink/common/feature_policy/feature_policy_unittest.cc b/third_party/blink/common/feature_policy/feature_policy_unittest.cc
index 7b30aec..1f8294f 100644
--- a/third_party/blink/common/feature_policy/feature_policy_unittest.cc
+++ b/third_party/blink/common/feature_policy/feature_policy_unittest.cc
@@ -143,8 +143,11 @@
CreateFromParentPolicy(nullptr, origin_a_);
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
- policy2->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_b_}}}});
+ policy2->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}});
EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultSelfFeature));
}
@@ -166,8 +169,11 @@
// they are at a different origin.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_a_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_a_);
std::unique_ptr<FeaturePolicy> policy3 =
@@ -199,8 +205,11 @@
// it is embedded by frame 2, for which the feature is not enabled.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_a_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
std::unique_ptr<FeaturePolicy> policy3 =
@@ -227,8 +236,11 @@
// enabled.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_b_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
std::unique_ptr<FeaturePolicy> policy3 =
@@ -248,8 +260,9 @@
// Default-on feature should be disabled in top-level frame.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultOnFeature, false, false, std::vector<url::Origin>()}}});
+ policy1->SetHeaderPolicy({{{kDefaultOnFeature, false, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
EXPECT_FALSE(policy1->IsFeatureEnabled(kDefaultOnFeature));
}
@@ -265,8 +278,9 @@
// Feature should be disabled in child frame.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultOnFeature, false, false, std::vector<url::Origin>()}}});
+ policy1->SetHeaderPolicy({{{kDefaultOnFeature, false, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_a_);
EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultOnFeature));
@@ -286,8 +300,9 @@
CreateFromParentPolicy(nullptr, origin_a_);
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
- policy2->SetHeaderPolicy(
- {{{kDefaultOnFeature, false, false, std::vector<url::Origin>()}}});
+ policy2->SetHeaderPolicy({{{kDefaultOnFeature, false, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultOnFeature));
}
@@ -310,7 +325,11 @@
CreateFromParentPolicy(nullptr, origin_a_);
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
- policy2->SetHeaderPolicy({{{kDefaultOnFeature, false, false, {origin_b_}}}});
+ policy2->SetHeaderPolicy({{{kDefaultOnFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}});
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentPolicy(policy2.get(), origin_c_);
EXPECT_TRUE(policy2->IsFeatureEnabled(kDefaultOnFeature));
@@ -329,8 +348,9 @@
// Default-on feature should be disabled in cross-origin child frame.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultOnFeature, false, false, std::vector<url::Origin>()}}});
+ policy1->SetHeaderPolicy({{{kDefaultOnFeature, false, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultOnFeature));
@@ -352,8 +372,9 @@
// Feature should be enabled in top and second level; disabled in frame 3.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
std::unique_ptr<FeaturePolicy> policy3 =
@@ -379,7 +400,11 @@
// Feature should be disabled in frame 1; enabled in frames 2, 3 and 4.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy({{{kDefaultOnFeature, false, false, {origin_b_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultOnFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
std::unique_ptr<FeaturePolicy> policy3 =
@@ -408,8 +433,11 @@
// Feature should be disabled in frames 1 and 4; enabled in frames 2 and 3.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_b_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
std::unique_ptr<FeaturePolicy> policy3 =
@@ -438,15 +466,27 @@
// Feature should be disabled in frames 1, 3 and 4; enabled in frame 2 only.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy({{{kDefaultOffFeature, false, false, {origin_b_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
- policy2->SetHeaderPolicy({{{kDefaultOffFeature, false, false, {origin_b_}}}});
+ policy2->SetHeaderPolicy({{{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}});
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentPolicy(policy2.get(), origin_b_);
std::unique_ptr<FeaturePolicy> policy4 =
CreateFromParentPolicy(policy2.get(), origin_c_);
- policy4->SetHeaderPolicy({{{kDefaultOffFeature, false, false, {origin_c_}}}});
+ policy4->SetHeaderPolicy({{{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_c_}}}});
EXPECT_FALSE(policy1->IsFeatureEnabled(kDefaultOffFeature));
EXPECT_TRUE(policy2->IsFeatureEnabled(kDefaultOffFeature));
EXPECT_FALSE(policy3->IsFeatureEnabled(kDefaultOffFeature));
@@ -469,12 +509,14 @@
// Feature should be enabled in all frames.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
- policy2->SetHeaderPolicy(
- {{{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}});
+ policy2->SetHeaderPolicy({{{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentPolicy(policy2.get(), origin_a_);
EXPECT_TRUE(policy1->IsFeatureEnabled(kDefaultSelfFeature));
@@ -498,12 +540,16 @@
// Feature should be enabled at the top level; disabled in all other frames.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_a_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
- policy2->SetHeaderPolicy(
- {{{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}});
+ policy2->SetHeaderPolicy({{{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentPolicy(policy2.get(), origin_a_);
std::unique_ptr<FeaturePolicy> policy4 =
@@ -530,12 +576,18 @@
// Feature should be enabled in all frames.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_a_, origin_b_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_, origin_b_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
- policy2->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_b_, origin_c_}}}});
+ policy2->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_, origin_c_}}}});
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentPolicy(policy2.get(), origin_c_);
EXPECT_TRUE(policy1->IsFeatureEnabled(kDefaultSelfFeature));
@@ -559,8 +611,11 @@
// Feature should be enabled in frames 1, 2, and 3, and disabled in frame 4.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultOnFeature, false, false, {origin_a_, origin_b_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultOnFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_, origin_b_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
std::unique_ptr<FeaturePolicy> policy3 =
@@ -590,8 +645,11 @@
// 4.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_a_, origin_b_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_, origin_b_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
std::unique_ptr<FeaturePolicy> policy3 =
@@ -623,14 +681,24 @@
// should be enabled in frame 1, and disabled in frames 2 and 3.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_a_, origin_b_}},
- {kDefaultOnFeature, false, false, {origin_a_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_, origin_b_}},
+ {kDefaultOnFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_}}}});
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentPolicy(policy1.get(), origin_b_);
policy2->SetHeaderPolicy(
- {{{kDefaultSelfFeature, true, false, std::vector<url::Origin>()},
- {kDefaultOnFeature, true, false, std::vector<url::Origin>()}}});
+ {{{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce, std::vector<url::Origin>()},
+ {kDefaultOnFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentPolicy(policy2.get(), origin_c_);
EXPECT_TRUE(policy1->IsFeatureEnabled(kDefaultSelfFeature));
@@ -650,8 +718,11 @@
// and disabled for origin C.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultOffFeature, false, false, {origin_a_, origin_b_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_, origin_b_}}}});
EXPECT_TRUE(
policy1->IsFeatureEnabledForOrigin(kDefaultOffFeature, origin_a_));
EXPECT_TRUE(
@@ -680,7 +751,11 @@
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
ParsedFeaturePolicy frame_policy = {
- {{kDefaultSelfFeature, false, false, {origin_b_}}}};
+ {{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
EXPECT_TRUE(
@@ -715,7 +790,9 @@
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
ParsedFeaturePolicy frame_policy = {
- {{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
EXPECT_TRUE(
@@ -761,11 +838,19 @@
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
ParsedFeaturePolicy frame_policy1 = {
- {{kDefaultSelfFeature, false, false, {origin_b_}}}};
+ {{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_b_);
ParsedFeaturePolicy frame_policy2 = {
- {{kDefaultSelfFeature, false, false, {origin_c_}}}};
+ {{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_c_}}}};
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentWithFramePolicy(policy2.get(), frame_policy2, origin_c_);
std::unique_ptr<FeaturePolicy> policy4 =
@@ -806,11 +891,15 @@
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
ParsedFeaturePolicy frame_policy1 = {
- {{kDefaultOnFeature, false, false, std::vector<url::Origin>()}}};
+ {{kDefaultOnFeature, false, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_a_);
ParsedFeaturePolicy frame_policy2 = {
- {{kDefaultOnFeature, false, false, std::vector<url::Origin>()}}};
+ {{kDefaultOnFeature, false, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy2, origin_b_);
EXPECT_TRUE(policy1->IsFeatureEnabledForOrigin(kDefaultOnFeature, origin_a_));
@@ -851,13 +940,25 @@
// child frames because they did not declare their own policy to enable it.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy({{{kDefaultOffFeature, false, false, {origin_a_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_}}}});
ParsedFeaturePolicy frame_policy1 = {
- {{kDefaultOffFeature, false, false, {origin_a_}}}};
+ {{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_}}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_a_);
ParsedFeaturePolicy frame_policy2 = {
- {{kDefaultOffFeature, false, false, {origin_b_}}}};
+ {{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}};
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy2, origin_b_);
EXPECT_TRUE(
@@ -902,17 +1003,37 @@
// they declare their own policy to enable it.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy({{{kDefaultOffFeature, false, false, {origin_a_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_}}}});
ParsedFeaturePolicy frame_policy1 = {
- {{kDefaultOffFeature, false, false, {origin_a_}}}};
+ {{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_}}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_a_);
- policy2->SetHeaderPolicy({{{kDefaultOffFeature, false, false, {origin_a_}}}});
+ policy2->SetHeaderPolicy({{{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_}}}});
ParsedFeaturePolicy frame_policy2 = {
- {{kDefaultOffFeature, false, false, {origin_b_}}}};
+ {{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}};
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy2, origin_b_);
- policy3->SetHeaderPolicy({{{kDefaultOffFeature, false, false, {origin_b_}}}});
+ policy3->SetHeaderPolicy({{{kDefaultOffFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}});
EXPECT_TRUE(
policy1->IsFeatureEnabledForOrigin(kDefaultOffFeature, origin_a_));
EXPECT_FALSE(
@@ -956,18 +1077,28 @@
// policy.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_a_, origin_b_}}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_, origin_b_}}}});
ParsedFeaturePolicy frame_policy1 = {
- {{kDefaultSelfFeature, false, false, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, false, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_b_);
ParsedFeaturePolicy frame_policy2 = {
- {{kDefaultSelfFeature, false, false, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, false, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy2, origin_b_);
- policy3->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, {origin_b_}}}});
+ policy3->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}});
EXPECT_FALSE(
policy2->IsFeatureEnabledForOrigin(kDefaultSelfFeature, origin_b_));
EXPECT_FALSE(
@@ -1002,13 +1133,20 @@
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
ParsedFeaturePolicy frame_policy1 = {
- {{kDefaultSelfFeature, false, false, {origin_b_}}}};
+ {{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_b_);
- policy2->SetHeaderPolicy(
- {{{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}});
+ policy2->SetHeaderPolicy({{{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
ParsedFeaturePolicy frame_policy2 = {
- {{kDefaultSelfFeature, false, false, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, false, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentWithFramePolicy(policy2.get(), frame_policy2, origin_c_);
std::unique_ptr<FeaturePolicy> policy4 =
@@ -1043,14 +1181,21 @@
// Default-self feature should be disabled in all frames.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, false, false, std::vector<url::Origin>()}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature, false, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
ParsedFeaturePolicy frame_policy1 = {
- {{kDefaultSelfFeature, false, false, {origin_b_}}}};
+ {{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_b_);
ParsedFeaturePolicy frame_policy2 = {
- {{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy2, origin_a_);
EXPECT_FALSE(
@@ -1088,17 +1233,31 @@
// enabled in the remaining frames.
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy({{kDefaultSelfFeature, false, false, {origin_b_}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}});
ParsedFeaturePolicy frame_policy1 = {
- {{kDefaultSelfFeature, false, false, {origin_a_}}}};
+ {{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_a_}}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_b_);
ParsedFeaturePolicy frame_policy2 = {
- {{kDefaultSelfFeature, false, false, {origin_b_}}}};
+ {{kDefaultSelfFeature,
+ false,
+ false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ {origin_b_}}}};
std::unique_ptr<FeaturePolicy> policy3 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy2, origin_b_);
ParsedFeaturePolicy frame_policy3 = {
- {{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy4 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy3, origin_b_);
EXPECT_FALSE(
@@ -1164,7 +1323,9 @@
CreateFromParentPolicy(nullptr, origin_a_);
url::Origin sandboxed_origin = url::Origin();
ParsedFeaturePolicy frame_policy = {
- {{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy2 = CreateFromParentWithFramePolicy(
policy1.get(), frame_policy, sandboxed_origin);
EXPECT_TRUE(policy2->IsFeatureEnabledForOrigin(kDefaultOnFeature, origin_a_));
@@ -1195,7 +1356,9 @@
CreateFromParentPolicy(nullptr, origin_a_);
url::Origin sandboxed_origin = url::Origin();
ParsedFeaturePolicy frame_policy = {
- {{kDefaultSelfFeature, false, true, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, false, true,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy2 = CreateFromParentWithFramePolicy(
policy1.get(), frame_policy, sandboxed_origin);
EXPECT_TRUE(policy2->IsFeatureEnabledForOrigin(kDefaultOnFeature, origin_a_));
@@ -1223,11 +1386,14 @@
// However, it will not pass that on to any other origin
std::unique_ptr<FeaturePolicy> policy1 =
CreateFromParentPolicy(nullptr, origin_a_);
- policy1->SetHeaderPolicy(
- {{{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}});
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}});
url::Origin sandboxed_origin = url::Origin();
ParsedFeaturePolicy frame_policy = {
- {{kDefaultSelfFeature, false, true, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, false, true,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy2 = CreateFromParentWithFramePolicy(
policy1.get(), frame_policy, sandboxed_origin);
EXPECT_TRUE(policy2->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1260,7 +1426,9 @@
url::Origin sandboxed_origin_1 = url::Origin();
url::Origin sandboxed_origin_2 = url::Origin();
ParsedFeaturePolicy frame_policy = {
- {{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy2 = CreateFromParentWithFramePolicy(
policy1.get(), frame_policy, sandboxed_origin_1);
std::unique_ptr<FeaturePolicy> policy3 =
@@ -1304,11 +1472,15 @@
url::Origin sandboxed_origin_1 = origin_a_.DeriveNewOpaqueOrigin();
url::Origin sandboxed_origin_2 = sandboxed_origin_1.DeriveNewOpaqueOrigin();
ParsedFeaturePolicy frame_policy_1 = {
- {{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy2 = CreateFromParentWithFramePolicy(
policy1.get(), frame_policy_1, sandboxed_origin_1);
ParsedFeaturePolicy frame_policy_2 = {
- {{kDefaultSelfFeature, true, false, std::vector<url::Origin>()}}};
+ {{kDefaultSelfFeature, true, false,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy3 = CreateFromParentWithFramePolicy(
policy2.get(), frame_policy_2, sandboxed_origin_2);
EXPECT_TRUE(policy3->IsFeatureEnabledForOrigin(kDefaultOnFeature, origin_a_));
@@ -1340,8 +1512,10 @@
CreateFromParentPolicy(nullptr, origin_a_);
ParsedFeaturePolicy frame_policy = {
{{mojom::FeaturePolicyFeature::kNotFound, false, true,
- std::vector<url::Origin>()},
- {kUnavailableFeature, false, true, std::vector<url::Origin>()}}};
+ mojom::FeaturePolicyDisposition::kEnforce, std::vector<url::Origin>()},
+ {kUnavailableFeature, false, true,
+ mojom::FeaturePolicyDisposition::kEnforce,
+ std::vector<url::Origin>()}}};
std::unique_ptr<FeaturePolicy> policy2 =
CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
EXPECT_FALSE(PolicyContainsInheritedValue(
@@ -1354,4 +1528,19 @@
PolicyContainsInheritedValue(policy2.get(), kUnavailableFeature));
}
+TEST_F(FeaturePolicyTest, TestReportOnlyFeaturesIncludedInHeader) {
+ // +---------------------------------------------------+
+ // |(1)Origin A |
+ // |Feature-Policy: default-self-report-only 'none' |
+ // +---------------------------------------------------+
+ // A feature which is tagged as '-report-only' should be included in the
+ // reporting policy.
+ std::unique_ptr<FeaturePolicy> policy1 =
+ CreateFromParentPolicy(nullptr, origin_a_);
+ policy1->SetHeaderPolicy({{{kDefaultSelfFeature, false, false,
+ mojom::FeaturePolicyDisposition::kReport,
+ std::vector<url::Origin>()}}});
+ EXPECT_FALSE(policy1->IsFeatureEnabled(kDefaultSelfFeature));
+}
+
} // namespace blink
diff --git a/third_party/blink/public/common/feature_policy/feature_policy.h b/third_party/blink/public/common/feature_policy/feature_policy.h
index f90a7ab..e929f47 100644
--- a/third_party/blink/public/common/feature_policy/feature_policy.h
+++ b/third_party/blink/public/common/feature_policy/feature_policy.h
@@ -92,6 +92,7 @@
ParsedFeaturePolicyDeclaration(mojom::FeaturePolicyFeature feature,
bool matches_all_origins,
bool matches_opaque_src,
+ mojom::FeaturePolicyDisposition disposition,
std::vector<url::Origin> origins);
ParsedFeaturePolicyDeclaration(const ParsedFeaturePolicyDeclaration& rhs);
ParsedFeaturePolicyDeclaration& operator=(
@@ -106,6 +107,7 @@
// of the iframe to be present in |origins|, but for sandboxed iframes, this
// flag is set instead.
bool matches_opaque_src;
+ mojom::FeaturePolicyDisposition disposition;
std::vector<url::Origin> origins;
};
@@ -114,6 +116,13 @@
bool BLINK_COMMON_EXPORT operator==(const ParsedFeaturePolicyDeclaration& lhs,
const ParsedFeaturePolicyDeclaration& rhs);
+// ParsedFeaturePolicy objects can contain directives of both enforcing and
+// report-only dispositions. This utility function will extract just the items
+// of one disposition or the other.
+BLINK_COMMON_EXPORT std::unique_ptr<ParsedFeaturePolicy>
+DirectivesWithDisposition(mojom::FeaturePolicyDisposition disposition,
+ const ParsedFeaturePolicy& policy);
+
class BLINK_COMMON_EXPORT FeaturePolicy {
public:
// Represents a collection of origins which make up an allowlist in a feature
diff --git a/third_party/blink/public/mojom/feature_policy/feature_policy.mojom b/third_party/blink/public/mojom/feature_policy/feature_policy.mojom
index 508e0c03..63d4b44 100644
--- a/third_party/blink/public/mojom/feature_policy/feature_policy.mojom
+++ b/third_party/blink/public/mojom/feature_policy/feature_policy.mojom
@@ -105,6 +105,13 @@
// chromium/src/tools/metrics/histograms/ to update the UMA mapping.
};
+// This enum is used to distinguish between report-only directives and enforcing
+// directives.
+enum FeaturePolicyDisposition {
+ kEnforce,
+ kReport,
+};
+
// This struct holds feature policy allowlist data that needs to be replicated
// between a RenderFrame and any of its associated RenderFrameProxies. A list of
// these form a ParsedFeaturePolicy.
@@ -112,5 +119,6 @@
struct ParsedFeaturePolicyDeclaration {
FeaturePolicyFeature feature;
bool matches_all_origins;
+ FeaturePolicyDisposition disposition;
array<url.mojom.Origin> origins;
};
diff --git a/third_party/blink/public/platform/reporting.mojom b/third_party/blink/public/platform/reporting.mojom
index c9e9127..f1da9a34 100644
--- a/third_party/blink/public/platform/reporting.mojom
+++ b/third_party/blink/public/platform/reporting.mojom
@@ -51,6 +51,7 @@
// (See //third_party/blink/renderer/core/frame/feature_policy_violation_report_body.h.)
QueueFeaturePolicyViolationReport(url.mojom.Url url,
string policy,
+ string disposition,
string message,
string? source_file,
int32 line_number,
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 227799f..196a751 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -7676,8 +7676,10 @@
return *lazy_load_image_observer_;
}
-void Document::ReportFeaturePolicyViolation(mojom::FeaturePolicyFeature feature,
- const String& message) const {
+void Document::ReportFeaturePolicyViolation(
+ mojom::FeaturePolicyFeature feature,
+ mojom::FeaturePolicyDisposition disposition,
+ const String& message) const {
if (!RuntimeEnabledFeatures::FeaturePolicyReportingEnabled())
return;
LocalFrame* frame = GetFrame();
@@ -7685,7 +7687,10 @@
return;
const String& feature_name = GetNameForFeature(feature);
FeaturePolicyViolationReportBody* body = new FeaturePolicyViolationReportBody(
- feature_name, "Feature policy violation", SourceLocation::Capture());
+ feature_name, "Feature policy violation",
+ (disposition == mojom::FeaturePolicyDisposition::kReport ? "report"
+ : "enforce"),
+ SourceLocation::Capture());
Report* report = new Report("feature-policy", Url().GetString(), body);
ReportingContext::From(this)->QueueReport(report);
@@ -7697,13 +7702,19 @@
// Send the feature policy violation report to the Reporting API.
frame->GetReportingService()->QueueFeaturePolicyViolationReport(
- Url(), feature_name, "Feature policy violation", body->sourceFile(),
- line_number, column_number);
- frame->Console().AddMessage(ConsoleMessage::Create(
- kViolationMessageSource, kErrorMessageLevel,
- (message.IsEmpty() ? ("Feature policy violation: " + feature_name +
- " is not allowed in this document.")
- : message)));
+ Url(), feature_name,
+ (disposition == mojom::FeaturePolicyDisposition::kReport ? "report"
+ : "enforce"),
+ "Feature policy violation", body->sourceFile(), line_number,
+ column_number);
+ // TODO(iclelland): Report something different in report-only mode
+ if (disposition == mojom::FeaturePolicyDisposition::kEnforce) {
+ frame->Console().AddMessage(ConsoleMessage::Create(
+ kViolationMessageSource, kErrorMessageLevel,
+ (message.IsEmpty() ? ("Feature policy violation: " + feature_name +
+ " is not allowed in this document.")
+ : message)));
+ }
}
void Document::IncrementNumberOfCanvases() {
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index f1d89465..f2f5a59ff 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -1488,6 +1488,7 @@
void ReportFeaturePolicyViolation(
mojom::FeaturePolicyFeature,
+ mojom::FeaturePolicyDisposition,
const String& message = g_empty_string) const override;
bool IsParsedFeaturePolicy(mojom::FeaturePolicyFeature feature) const {
diff --git a/third_party/blink/renderer/core/execution_context/security_context.cc b/third_party/blink/renderer/core/execution_context/security_context.cc
index 60748fa6f..28651e7 100644
--- a/third_party/blink/renderer/core/execution_context/security_context.cc
+++ b/third_party/blink/renderer/core/execution_context/security_context.cc
@@ -111,32 +111,70 @@
feature_policy_ = std::move(feature_policy);
}
+// Uses the parent enforcing policy; parsed_header and container_policy can
+// both contain report-only directives, which will be used to construct the
+// report-only policy for this context.
void SecurityContext::InitializeFeaturePolicy(
const ParsedFeaturePolicy& parsed_header,
const ParsedFeaturePolicy& container_policy,
const FeaturePolicy* parent_feature_policy) {
+ report_only_feature_policy_ = nullptr;
if (!HasCustomizedFeaturePolicy()) {
feature_policy_ = FeaturePolicy::CreateFromParentPolicy(
nullptr, {}, security_origin_->ToUrlOrigin());
return;
}
+
feature_policy_ = FeaturePolicy::CreateFromParentPolicy(
- parent_feature_policy, container_policy, security_origin_->ToUrlOrigin());
- feature_policy_->SetHeaderPolicy(parsed_header);
+ parent_feature_policy,
+ *DirectivesWithDisposition(mojom::FeaturePolicyDisposition::kEnforce,
+ container_policy),
+ security_origin_->ToUrlOrigin());
+ feature_policy_->SetHeaderPolicy(*DirectivesWithDisposition(
+ mojom::FeaturePolicyDisposition::kEnforce, parsed_header));
+ if (RuntimeEnabledFeatures::FeaturePolicyReportingEnabled()) {
+ report_only_feature_policy_ = FeaturePolicy::CreateFromParentPolicy(
+ parent_feature_policy,
+ *DirectivesWithDisposition(mojom::FeaturePolicyDisposition::kReport,
+ container_policy),
+ security_origin_->ToUrlOrigin());
+ report_only_feature_policy_->SetHeaderPolicy(*DirectivesWithDisposition(
+ mojom::FeaturePolicyDisposition::kReport, parsed_header));
+ }
}
bool SecurityContext::IsFeatureEnabled(mojom::FeaturePolicyFeature feature,
ReportOptions report_on_failure,
const String& message) const {
+ FeatureEnabledState state = GetFeatureEnabledState(feature);
+ if (state == FeatureEnabledState::kEnabled)
+ return true;
+ if (report_on_failure == ReportOptions::kReportOnFailure &&
+ RuntimeEnabledFeatures::FeaturePolicyReportingEnabled()) {
+ ReportFeaturePolicyViolation(
+ feature,
+ (state == FeatureEnabledState::kReportOnly
+ ? mojom::FeaturePolicyDisposition::kReport
+ : mojom::FeaturePolicyDisposition::kEnforce),
+ message);
+ }
+ return (state != FeatureEnabledState::kDisabled);
+}
+
+FeatureEnabledState SecurityContext::GetFeatureEnabledState(
+ mojom::FeaturePolicyFeature feature) const {
// The policy should always be initialized before checking it to ensure we
// properly inherit the parent policy.
DCHECK(feature_policy_);
- if (feature_policy_->IsFeatureEnabled(feature))
- return true;
- if (report_on_failure == ReportOptions::kReportOnFailure)
- ReportFeaturePolicyViolation(feature, message);
- return false;
+ if (feature_policy_->IsFeatureEnabled(feature)) {
+ if (report_only_feature_policy_ &&
+ !report_only_feature_policy_->IsFeatureEnabled(feature)) {
+ return FeatureEnabledState::kReportOnly;
+ }
+ return FeatureEnabledState::kEnabled;
+ }
+ return FeatureEnabledState::kDisabled;
}
} // namespace blink
diff --git a/third_party/blink/renderer/core/execution_context/security_context.h b/third_party/blink/renderer/core/execution_context/security_context.h
index 52347473d..0257f3e 100644
--- a/third_party/blink/renderer/core/execution_context/security_context.h
+++ b/third_party/blink/renderer/core/execution_context/security_context.h
@@ -51,8 +51,10 @@
// Whether to report policy violations when checking whether a feature is
// enabled.
enum class ReportOptions { kReportOnFailure, kDoNotReport };
+enum class FeatureEnabledState { kDisabled, kReportOnly, kEnabled };
namespace mojom {
+enum class FeaturePolicyDisposition : int32_t;
enum class FeaturePolicyFeature : int32_t;
enum class IPAddressSpace : int32_t;
}
@@ -124,6 +126,9 @@
}
FeaturePolicy* GetFeaturePolicy() const { return feature_policy_.get(); }
+ FeaturePolicy* GetReportOnlyFeaturePolicy() const {
+ return report_only_feature_policy_.get();
+ }
void SetFeaturePolicy(std::unique_ptr<FeaturePolicy> feature_policy);
void InitializeFeaturePolicy(const ParsedFeaturePolicy& parsed_header,
const ParsedFeaturePolicy& container_policy,
@@ -138,8 +143,10 @@
mojom::FeaturePolicyFeature,
ReportOptions report_on_failure = ReportOptions::kDoNotReport,
const String& message = g_empty_string) const;
+ FeatureEnabledState GetFeatureEnabledState(mojom::FeaturePolicyFeature) const;
virtual void ReportFeaturePolicyViolation(
mojom::FeaturePolicyFeature,
+ mojom::FeaturePolicyDisposition,
const String& message = g_empty_string) const {}
// Apply the sandbox flag. In addition, if the origin is not already opaque,
@@ -165,6 +172,7 @@
scoped_refptr<SecurityOrigin> security_origin_;
Member<ContentSecurityPolicy> content_security_policy_;
std::unique_ptr<FeaturePolicy> feature_policy_;
+ std::unique_ptr<FeaturePolicy> report_only_feature_policy_;
mojom::IPAddressSpace address_space_;
WebInsecureRequestPolicy insecure_request_policy_;
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.cc b/third_party/blink/renderer/core/feature_policy/feature_policy.cc
index 5f6828e..63876c9 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy.cc
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy.cc
@@ -17,6 +17,9 @@
namespace blink {
+constexpr char kReportOnlySuffix[] = "-report-only";
+constexpr size_t kReportOnlySuffixLength = 12;
+
ParsedFeaturePolicy ParseFeaturePolicyHeader(
const String& policy,
scoped_refptr<const SecurityOrigin> origin,
@@ -64,15 +67,31 @@
// Empty policy. Skip.
if (tokens.IsEmpty())
continue;
- if (!feature_names.Contains(tokens[0])) {
- if (messages)
+ mojom::FeaturePolicyDisposition disposition =
+ mojom::FeaturePolicyDisposition::kEnforce;
+ String feature_name;
+ if (RuntimeEnabledFeatures::FeaturePolicyReportingEnabled() &&
+ tokens[0].EndsWith(kReportOnlySuffix)) {
+ feature_name = tokens[0].Substring(
+ 0, tokens[0].length() - kReportOnlySuffixLength);
+ disposition = mojom::FeaturePolicyDisposition::kReport;
+ } else {
+ feature_name = tokens[0];
+ }
+ if (!feature_names.Contains(feature_name)) {
+ if (messages) {
+ // Console message should display the entire string, with
+ // "-report-only" suffix if it was originally included.
messages->push_back("Unrecognized feature: '" + tokens[0] + "'.");
+ }
continue;
}
- mojom::FeaturePolicyFeature feature = feature_names.at(tokens[0]);
+ mojom::FeaturePolicyFeature feature = feature_names.at(feature_name);
// If a policy has already been specified for the current feature, drop
// the new policy.
+ // TODO(crbug.com/904880): Allow a report-only and an enforcing version in
+ // the same parsed policy.
if (features_specified.QuickGet(static_cast<int>(feature)))
continue;
@@ -92,6 +111,7 @@
ParsedFeaturePolicyDeclaration allowlist;
allowlist.feature = feature;
+ allowlist.disposition = disposition;
features_specified.QuickSet(static_cast<int>(feature));
std::vector<url::Origin> origins;
// If a policy entry has no (optional) values (e,g,
@@ -181,6 +201,7 @@
allowlist.feature = feature;
allowlist.matches_all_origins = false;
allowlist.matches_opaque_src = false;
+ allowlist.disposition = mojom::FeaturePolicyDisposition::kEnforce;
policy.push_back(allowlist);
return true;
}
@@ -193,6 +214,7 @@
allowlist.feature = feature;
allowlist.matches_all_origins = true;
allowlist.matches_opaque_src = true;
+ allowlist.disposition = mojom::FeaturePolicyDisposition::kEnforce;
policy.push_back(allowlist);
return true;
}
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc b/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc
index 3ec5bf5..54d36ab 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc
@@ -439,10 +439,12 @@
ParsedFeaturePolicy test_policy = {{mojom::FeaturePolicyFeature::kFullscreen,
false,
false,
+ mojom::FeaturePolicyDisposition::kEnforce,
{url_origin_a_, url_origin_b_}},
{mojom::FeaturePolicyFeature::kGeolocation,
false,
false,
+ mojom::FeaturePolicyDisposition::kEnforce,
{url_origin_a_}}};
ParsedFeaturePolicy empty_policy = {};
};
diff --git a/third_party/blink/renderer/core/feature_policy/iframe_policy.h b/third_party/blink/renderer/core/feature_policy/iframe_policy.h
index e53ec64..d4645da 100644
--- a/third_party/blink/renderer/core/feature_policy/iframe_policy.h
+++ b/third_party/blink/renderer/core/feature_policy/iframe_policy.h
@@ -33,7 +33,9 @@
const ParsedFeaturePolicy& container_policy,
scoped_refptr<const SecurityOrigin> src_origin) override {
policy_ = FeaturePolicy::CreateFromParentPolicy(
- parent_document_->GetFeaturePolicy(), container_policy,
+ parent_document_->GetFeaturePolicy(),
+ *DirectivesWithDisposition(mojom::FeaturePolicyDisposition::kEnforce,
+ container_policy),
src_origin->ToUrlOrigin());
}
diff --git a/third_party/blink/renderer/core/frame/feature_policy_violation_report_body.h b/third_party/blink/renderer/core/frame/feature_policy_violation_report_body.h
index b0a54f2e..ebe0794f 100644
--- a/third_party/blink/renderer/core/frame/feature_policy_violation_report_body.h
+++ b/third_party/blink/renderer/core/frame/feature_policy_violation_report_body.h
@@ -16,15 +16,20 @@
public:
FeaturePolicyViolationReportBody(const String& feature,
const String& message,
+ const String& disposition,
std::unique_ptr<SourceLocation> location)
- : MessageReportBody(message, std::move(location)), feature_(feature) {}
+ : MessageReportBody(message, std::move(location)),
+ feature_(feature),
+ disposition_(disposition) {}
String feature() const { return feature_; }
+ String disposition() const { return disposition_; }
~FeaturePolicyViolationReportBody() override = default;
private:
const String feature_;
+ const String disposition_;
};
} // namespace blink
diff --git a/third_party/blink/renderer/core/frame/feature_policy_violation_report_body.idl b/third_party/blink/renderer/core/frame/feature_policy_violation_report_body.idl
index 42f3ef1c..968088b0 100644
--- a/third_party/blink/renderer/core/frame/feature_policy_violation_report_body.idl
+++ b/third_party/blink/renderer/core/frame/feature_policy_violation_report_body.idl
@@ -8,6 +8,7 @@
NoInterfaceObject
] interface FeaturePolicyViolationReportBody : ReportBody {
readonly attribute DOMString feature;
+ readonly attribute DOMString disposition;
readonly attribute DOMString message;
readonly attribute DOMString? sourceFile;
readonly attribute unsigned long? lineNumber;
diff --git a/third_party/blink/renderer/core/html/html_frame_element.cc b/third_party/blink/renderer/core/html/html_frame_element.cc
index 46555b9..dc1bdd9 100644
--- a/third_party/blink/renderer/core/html/html_frame_element.cc
+++ b/third_party/blink/renderer/core/html/html_frame_element.cc
@@ -85,14 +85,15 @@
ParsedFeaturePolicy HTMLFrameElement::ConstructContainerPolicy(
Vector<String>*) const {
// Frame elements are not allowed to enable the fullscreen feature. Add an
- // empty whitelist for the fullscreen feature so that the framed content is
+ // empty allowlist for the fullscreen feature so that the framed content is
// unable to use the API, regardless of origin.
// https://fullscreen.spec.whatwg.org/#model
ParsedFeaturePolicy container_policy;
- ParsedFeaturePolicyDeclaration whitelist;
- whitelist.feature = mojom::FeaturePolicyFeature::kFullscreen;
- whitelist.matches_all_origins = false;
- container_policy.push_back(whitelist);
+ ParsedFeaturePolicyDeclaration allowlist;
+ allowlist.feature = mojom::FeaturePolicyFeature::kFullscreen;
+ allowlist.matches_all_origins = false;
+ allowlist.disposition = mojom::FeaturePolicyDisposition::kEnforce;
+ container_policy.push_back(allowlist);
return container_policy;
}
diff --git a/third_party/blink/renderer/core/html/html_plugin_element.cc b/third_party/blink/renderer/core/html/html_plugin_element.cc
index f7d52096..dec73f7 100644
--- a/third_party/blink/renderer/core/html/html_plugin_element.cc
+++ b/third_party/blink/renderer/core/html/html_plugin_element.cc
@@ -299,15 +299,16 @@
ParsedFeaturePolicy HTMLPlugInElement::ConstructContainerPolicy(
Vector<String>*) const {
// Plugin elements (<object> and <embed>) are not allowed to enable the
- // fullscreen feature. Add an empty whitelist for the fullscreen feature so
+ // fullscreen feature. Add an empty allowlist for the fullscreen feature so
// that the nested browsing context is unable to use the API, regardless of
// origin.
// https://fullscreen.spec.whatwg.org/#model
ParsedFeaturePolicy container_policy;
- ParsedFeaturePolicyDeclaration whitelist;
- whitelist.feature = mojom::FeaturePolicyFeature::kFullscreen;
- whitelist.matches_all_origins = false;
- container_policy.push_back(whitelist);
+ ParsedFeaturePolicyDeclaration allowlist;
+ allowlist.feature = mojom::FeaturePolicyFeature::kFullscreen;
+ allowlist.matches_all_origins = false;
+ allowlist.disposition = mojom::FeaturePolicyDisposition::kEnforce;
+ container_policy.push_back(allowlist);
return container_policy;
}
diff --git a/third_party/blink/renderer/core/html/media/media_element_parser_helpers.cc b/third_party/blink/renderer/core/html/media/media_element_parser_helpers.cc
index 8ac695d..8ab20da 100644
--- a/third_party/blink/renderer/core/html/media/media_element_parser_helpers.cc
+++ b/third_party/blink/renderer/core/html/media/media_element_parser_helpers.cc
@@ -69,7 +69,8 @@
if (!style.LogicalWidth().IsSpecified() &&
!style.LogicalHeight().IsSpecified()) {
layout_object->GetDocument().ReportFeaturePolicyViolation(
- mojom::FeaturePolicyFeature::kUnsizedMedia);
+ mojom::FeaturePolicyFeature::kUnsizedMedia,
+ mojom::FeaturePolicyDisposition::kEnforce);
}
}
diff --git a/third_party/blink/renderer/modules/webusb/usb.cc b/third_party/blink/renderer/modules/webusb/usb.cc
index 647b920..d41521b2 100644
--- a/third_party/blink/renderer/modules/webusb/usb.cc
+++ b/third_party/blink/renderer/modules/webusb/usb.cc
@@ -110,11 +110,19 @@
script_state,
DOMException::Create(DOMExceptionCode::kNotSupportedError));
}
- if (!IsFeatureEnabled()) {
+
+ FeatureEnabledState state = GetFeatureEnabledState();
+ if (state != FeatureEnabledState::kEnabled) {
ExecutionContext* execution_context = ExecutionContext::From(script_state);
if (auto* document = DynamicTo<Document>(execution_context)) {
- document->ReportFeaturePolicyViolation(mojom::FeaturePolicyFeature::kUsb);
+ document->ReportFeaturePolicyViolation(
+ mojom::FeaturePolicyFeature::kUsb,
+ (state == FeatureEnabledState::kReportOnly
+ ? mojom::FeaturePolicyDisposition::kReport
+ : mojom::FeaturePolicyDisposition::kEnforce));
}
+ }
+ if (state == FeatureEnabledState::kDisabled) {
return ScriptPromise::RejectWithDOMException(
script_state, DOMException::Create(DOMExceptionCode::kSecurityError,
kFeaturePolicyBlocked));
@@ -271,7 +279,8 @@
return;
}
- if (!IsContextSupported() || !IsFeatureEnabled())
+ if (!IsContextSupported() ||
+ GetFeatureEnabledState() == FeatureEnabledState::kDisabled)
return;
EnsureServiceConnection();
@@ -282,7 +291,7 @@
return;
DCHECK(IsContextSupported());
- DCHECK(IsFeatureEnabled());
+ DCHECK(GetFeatureEnabledState() != FeatureEnabledState::kDisabled);
GetExecutionContext()->GetInterfaceProvider()->GetInterface(
mojo::MakeRequest(&service_));
service_.set_connection_error_handler(
@@ -311,8 +320,8 @@
return true;
}
-bool USB::IsFeatureEnabled() const {
- return GetExecutionContext()->GetSecurityContext().IsFeatureEnabled(
+FeatureEnabledState USB::GetFeatureEnabledState() const {
+ return GetExecutionContext()->GetSecurityContext().GetFeatureEnabledState(
mojom::FeaturePolicyFeature::kUsb);
}
diff --git a/third_party/blink/renderer/modules/webusb/usb.h b/third_party/blink/renderer/modules/webusb/usb.h
index 8a37d0c..b19b102 100644
--- a/third_party/blink/renderer/modules/webusb/usb.h
+++ b/third_party/blink/renderer/modules/webusb/usb.h
@@ -12,6 +12,7 @@
#include "third_party/blink/renderer/core/dom/context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/execution_context/security_context.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
@@ -79,7 +80,7 @@
void EnsureServiceConnection();
bool IsContextSupported() const;
- bool IsFeatureEnabled() const;
+ FeatureEnabledState GetFeatureEnabledState() const;
mojom::blink::WebUsbServicePtr service_;
HeapHashSet<Member<ScriptPromiseResolver>> get_devices_requests_;