0

Reland "[Credentialless] Add credentialless reporting"

This is a reland of 9787b3957b

Diff here:
https://chromium-review.googlesource.com/c/chromium/src/+/2940593/1..8
which add the report link and fix wpt tests in
`reporting-to-endpoint.https.html` and unit test in
`cross_origin_resource_policy_unittest.cc` alone with
a diff of test in
`credentialless/reporting-subresource-corp.tentative.https.html`
which related to CORP issue landed in
https://chromium-review.googlesource.com/c/chromium/src/+/2886899

Original change's description:
> [Credentialless] Add credentialless reporting
>
> This CL adds support for navigational COEP:credentialless
> requests to cross-origin-resource-policy reporting.
> When a navigational response is blocked, COEP:credentialless
> report will be sent.
>
> Bug: 1200849, 1215583
> Change-Id: I3ab8235190597b6292f99afe5daffd059435d369
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2867085
> Reviewed-by: Yifan Luo <lyf@chromium.org>
> Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
> Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
> Commit-Queue: Yifan Luo <lyf@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#888454}

Bug: 1200849
Change-Id: Ib1fe3721de61cbea1b9284d07bc0a09b5923f474
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2940593
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Commit-Queue: Yifan Luo <lyf@chromium.org>
Cr-Commit-Position: refs/heads/master@{#890720}
This commit is contained in:
Yifan Luo
2021-06-09 12:47:29 +00:00
committed by Chromium LUCI CQ
parent a70e32b72c
commit a9b5392e1a
7 changed files with 503 additions and 18 deletions

@ -238,8 +238,11 @@ absl::optional<mojom::BlockedByResponseReason> IsBlockedInternalWithReporting(
mojom::CrossOriginEmbedderPolicyReporter* reporter) {
constexpr auto kBlockedDueToCoep = mojom::BlockedByResponseReason::
kCorpNotSameOriginAfterDefaultedToSameOriginByCoep;
if (embedder_policy.report_only_value ==
mojom::CrossOriginEmbedderPolicyValue::kRequireCorp &&
if ((embedder_policy.report_only_value ==
mojom::CrossOriginEmbedderPolicyValue::kRequireCorp ||
(embedder_policy.report_only_value ==
mojom::CrossOriginEmbedderPolicyValue::kCredentialless &&
request_mode == mojom::RequestMode::kNavigate)) &&
reporter) {
const auto result = IsBlockedInternal(
policy, request_url, request_initiator, request_mode,

@ -6,9 +6,11 @@
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/test/scoped_feature_list.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/cross_origin_resource_policy.h"
#include "services/network/public/cpp/features.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
@ -155,6 +157,10 @@ TEST(CrossOriginResourcePolicyTest, ShouldAllowSameSite) {
}
TEST(CrossOriginResourcePolicyTest, WithCOEP) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kCrossOriginEmbedderPolicyCredentialless}, {});
mojom::URLResponseHead corp_none;
mojom::URLResponseHead corp_same_origin;
mojom::URLResponseHead corp_cross_origin;
@ -189,29 +195,34 @@ TEST(CrossOriginResourcePolicyTest, WithCOEP) {
expectation_with_coep_none;
const absl::optional<mojom::BlockedByResponseReason>
expectation_with_coep_require_corp;
const absl::optional<mojom::BlockedByResponseReason>
expectation_with_coep_credentialless;
} test_cases[] = {
// We don't have a cross-origin-resource-policy header on a response. That
// leads to blocking when COEP: kRequireCorp is used.
{RequestMode::kNoCors, another_origin, corp_none.Clone(), kAllow,
mojom::BlockedByResponseReason::
kCorpNotSameOriginAfterDefaultedToSameOriginByCoep,
mojom::BlockedByResponseReason::
kCorpNotSameOriginAfterDefaultedToSameOriginByCoep},
// We have "cross-origin-resource-policy: same-origin", so regardless of
// COEP the response is blocked.
{RequestMode::kNoCors, another_origin, corp_same_origin.Clone(),
mojom::BlockedByResponseReason::kCorpNotSameOrigin,
mojom::BlockedByResponseReason::kCorpNotSameOrigin,
mojom::BlockedByResponseReason::kCorpNotSameOrigin},
// We have "cross-origin-resource-policy: cross-origin", so regardless of
// COEP the response is allowed.
{RequestMode::kNoCors, another_origin, corp_cross_origin.Clone(), kAllow,
kAllow},
kAllow, kAllow},
// The origin of the request URL and request's origin match, so regardless
// of COEP the response is allowed.
{RequestMode::kNoCors, destination_origin, corp_same_origin.Clone(),
kAllow, kAllow},
kAllow, kAllow, kAllow},
// The request mode is "cors", so so regardless of COEP the response is
// allowed.
{RequestMode::kCors, another_origin, corp_same_origin.Clone(), kAllow,
kAllow},
kAllow, kAllow},
};
for (const auto& test_case : test_cases) {
@ -291,10 +302,70 @@ TEST(CrossOriginResourcePolicyTest, WithCOEP) {
} else {
EXPECT_TRUE(reporter.reports().empty());
}
reporter.ClearReports();
// COEP: credentialless, COEP-report-only: none
embedder_policy.value =
mojom::CrossOriginEmbedderPolicyValue::kCredentialless;
EXPECT_EQ(
test_case.expectation_with_coep_credentialless,
CrossOriginResourcePolicy::IsBlocked(
final_url, original_url, test_case.origin, *test_case.response_info,
test_case.request_mode, test_case.origin,
RequestDestination::kImage, embedder_policy, &reporter));
if (should_be_blocked_due_to_coep) {
ASSERT_EQ(2u, reporter.reports().size());
EXPECT_TRUE(reporter.reports()[0].report_only);
EXPECT_EQ(reporter.reports()[0].blocked_url, original_url);
EXPECT_EQ(reporter.reports()[0].destination, RequestDestination::kImage);
EXPECT_FALSE(reporter.reports()[1].report_only);
EXPECT_EQ(reporter.reports()[1].blocked_url, original_url);
EXPECT_EQ(reporter.reports()[1].destination, RequestDestination::kImage);
} else {
EXPECT_TRUE(reporter.reports().empty());
}
reporter.ClearReports();
// COEP: none, COEP-report-only: credentialless
embedder_policy.value = mojom::CrossOriginEmbedderPolicyValue::kNone;
embedder_policy.report_only_value =
mojom::CrossOriginEmbedderPolicyValue::kCredentialless;
EXPECT_EQ(
test_case.expectation_with_coep_none,
CrossOriginResourcePolicy::IsBlocked(
final_url, original_url, test_case.origin, *test_case.response_info,
test_case.request_mode, test_case.origin,
RequestDestination::kScript, embedder_policy, &reporter));
EXPECT_TRUE(reporter.reports().empty());
reporter.ClearReports();
// COEP: credentialless, COEP-report-only: credentialless
embedder_policy.value =
mojom::CrossOriginEmbedderPolicyValue::kCredentialless;
embedder_policy.report_only_value =
mojom::CrossOriginEmbedderPolicyValue::kCredentialless;
EXPECT_EQ(
test_case.expectation_with_coep_credentialless,
CrossOriginResourcePolicy::IsBlocked(
final_url, original_url, test_case.origin, *test_case.response_info,
test_case.request_mode, test_case.origin,
RequestDestination::kEmpty, embedder_policy, &reporter));
if (should_be_blocked_due_to_coep) {
ASSERT_EQ(1u, reporter.reports().size());
EXPECT_FALSE(reporter.reports()[0].report_only);
EXPECT_EQ(reporter.reports()[0].blocked_url, original_url);
EXPECT_EQ(reporter.reports()[0].destination, RequestDestination::kEmpty);
} else {
EXPECT_TRUE(reporter.reports().empty());
}
}
}
TEST(CrossOriginResourcePolicyTest, NavigationWithCOEP) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kCrossOriginEmbedderPolicyCredentialless}, {});
mojom::URLResponseHead corp_none;
mojom::URLResponseHead corp_same_origin;
mojom::URLResponseHead corp_cross_origin;
@ -328,22 +399,27 @@ TEST(CrossOriginResourcePolicyTest, NavigationWithCOEP) {
expectation_with_coep_none;
const absl::optional<mojom::BlockedByResponseReason>
expectation_with_coep_require_corp;
const absl::optional<mojom::BlockedByResponseReason>
expectation_with_coep_credentialless;
} test_cases[] = {
// We don't have a cross-origin-resource-policy header on a response. That
// leads to blocking when COEP: kRequireCorp is used.
{another_origin, corp_none.Clone(), kAllow,
mojom::BlockedByResponseReason::
kCorpNotSameOriginAfterDefaultedToSameOriginByCoep,
mojom::BlockedByResponseReason::
kCorpNotSameOriginAfterDefaultedToSameOriginByCoep},
// We have "cross-origin-resource-policy: same-origin",
// COEP the response is blocked.
{another_origin, corp_same_origin.Clone(), kAllow,
mojom::BlockedByResponseReason::kCorpNotSameOrigin,
mojom::BlockedByResponseReason::kCorpNotSameOrigin},
// We have "cross-origin-resource-policy: cross-origin", so regardless of
// COEP the response is allowed.
{another_origin, corp_cross_origin.Clone(), kAllow, kAllow},
{another_origin, corp_cross_origin.Clone(), kAllow, kAllow, kAllow},
// The origin of the request URL and request's origin match, so regardless
// of COEP the response is allowed.
{destination_origin, corp_same_origin.Clone(), kAllow, kAllow},
{destination_origin, corp_same_origin.Clone(), kAllow, kAllow, kAllow},
};
for (const auto& test_case : test_cases) {
@ -352,6 +428,9 @@ TEST(CrossOriginResourcePolicyTest, NavigationWithCOEP) {
const bool should_be_blocked_due_to_coep =
(test_case.expectation_with_coep_none !=
test_case.expectation_with_coep_require_corp);
const bool should_be_blocked_due_to_credentialless =
(test_case.expectation_with_coep_none !=
test_case.expectation_with_coep_credentialless);
// COEP: none, COEP-report-only: none
EXPECT_EQ(test_case.expectation_with_coep_none,
@ -419,6 +498,69 @@ TEST(CrossOriginResourcePolicyTest, NavigationWithCOEP) {
} else {
EXPECT_TRUE(reporter.reports().empty());
}
reporter.ClearReports();
// COEP: credentialless, COEP-report-only: none
embedder_policy.value =
mojom::CrossOriginEmbedderPolicyValue::kCredentialless;
EXPECT_EQ(test_case.expectation_with_coep_credentialless,
CrossOriginResourcePolicy::IsNavigationBlocked(
final_url, original_url, test_case.origin,
*test_case.response_info, test_case.origin,
RequestDestination::kImage, embedder_policy, &reporter));
if (should_be_blocked_due_to_credentialless) {
ASSERT_EQ(2u, reporter.reports().size());
EXPECT_TRUE(reporter.reports()[0].report_only);
EXPECT_EQ(reporter.reports()[0].blocked_url, original_url);
EXPECT_EQ(reporter.reports()[0].destination, RequestDestination::kImage);
EXPECT_FALSE(reporter.reports()[1].report_only);
EXPECT_EQ(reporter.reports()[1].blocked_url, original_url);
EXPECT_EQ(reporter.reports()[1].destination, RequestDestination::kImage);
} else {
EXPECT_TRUE(reporter.reports().empty());
}
reporter.ClearReports();
// COEP: none, COEP-report-only: credentialless
embedder_policy.value = mojom::CrossOriginEmbedderPolicyValue::kNone;
embedder_policy.report_only_value =
mojom::CrossOriginEmbedderPolicyValue::kCredentialless;
EXPECT_EQ(test_case.expectation_with_coep_none,
CrossOriginResourcePolicy::IsNavigationBlocked(
final_url, original_url, test_case.origin,
*test_case.response_info, test_case.origin,
RequestDestination::kScript, embedder_policy, &reporter));
if (should_be_blocked_due_to_credentialless) {
ASSERT_EQ(1u, reporter.reports().size());
EXPECT_TRUE(reporter.reports()[0].report_only);
EXPECT_EQ(reporter.reports()[0].blocked_url, original_url);
EXPECT_EQ(reporter.reports()[0].destination, RequestDestination::kScript);
} else {
EXPECT_TRUE(reporter.reports().empty());
}
reporter.ClearReports();
// COEP: credentialless, COEP-report-only: credentialless
embedder_policy.value =
mojom::CrossOriginEmbedderPolicyValue::kCredentialless;
embedder_policy.report_only_value =
mojom::CrossOriginEmbedderPolicyValue::kCredentialless;
EXPECT_EQ(test_case.expectation_with_coep_credentialless,
CrossOriginResourcePolicy::IsNavigationBlocked(
final_url, original_url, test_case.origin,
*test_case.response_info, test_case.origin,
RequestDestination::kEmpty, embedder_policy, &reporter));
if (should_be_blocked_due_to_credentialless) {
ASSERT_EQ(2u, reporter.reports().size());
EXPECT_TRUE(reporter.reports()[0].report_only);
EXPECT_EQ(reporter.reports()[0].blocked_url, original_url);
EXPECT_EQ(reporter.reports()[0].destination, RequestDestination::kEmpty);
EXPECT_FALSE(reporter.reports()[1].report_only);
EXPECT_EQ(reporter.reports()[1].blocked_url, original_url);
EXPECT_EQ(reporter.reports()[1].destination, RequestDestination::kEmpty);
} else {
EXPECT_TRUE(reporter.reports().empty());
}
}
}
} // namespace network

@ -0,0 +1,136 @@
<!doctype html>
<html>
<meta name="timeout" content="long">
<body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="./resources/common.js"></script>
<script>
const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
const COEP = '|header(cross-origin-embedder-policy,credentialless)';
const COEP_RO =
'|header(cross-origin-embedder-policy-report-only,credentialless)';
const CORP_CROSS_ORIGIN =
'|header(cross-origin-resource-policy,cross-origin)';
const FRAME_URL = `${ORIGIN}/common/blank.html?pipe=`;
const REMOTE_FRAME_URL = `${REMOTE_ORIGIN}/common/blank.html?pipe=`;
function checkCorpReport(report, contextUrl, blockedUrl, disposition) {
assert_equals(report.type, 'coep');
assert_equals(report.url, contextUrl);
assert_equals(report.body.type, 'corp');
assert_equals(report.body.blockedURL, blockedUrl);
assert_equals(report.body.disposition, disposition);
assert_equals(report.body.destination, 'iframe');
}
function checkCoepMismatchReport(report, contextUrl, blockedUrl, disposition) {
assert_equals(report.type, 'coep');
assert_equals(report.url, contextUrl);
assert_equals(report.body.type, 'navigation');
assert_equals(report.body.blockedURL, blockedUrl);
assert_equals(report.body.disposition, disposition);
}
function loadFrame(document, url) {
return new Promise((resolve, reject) => {
const frame = document.createElement('iframe');
frame.src = url;
frame.onload = () => resolve(frame);
frame.onerror = reject;
document.body.appendChild(frame);
});
}
// |parentSuffix| is a suffix for the parent frame URL.
// |targetUrl| is a URL for the target frame.
async function loadFrames(test, parentSuffix, targetUrl) {
const frame = await loadFrame(document, FRAME_URL + parentSuffix);
test.add_cleanup(() => frame.remove());
// Here we don't need "await". This loading may or may not succeed, and
// we're not interested in the result.
loadFrame(frame.contentDocument, targetUrl);
return frame;
}
async function observeReports(global) {
const reports = [];
const observer = new global.ReportingObserver((rs) => {
for (const r of rs) {
reports.push(r.toJSON());
}
});
observer.observe();
// Wait 1000ms for reports to settle.
await new Promise(r => step_timeout(r, 1000));
return reports;
}
function desc(headers) {
return headers === '' ? '(none)' : headers;
}
// CASES is a list of test case. Each test case consists of:
// parent_headers: the suffix of the URL of the parent frame.
// target_headers: the suffix of the URL of the target frame.
// expected_reports: one of:
// 'CORP': CORP violation
// 'CORP-RO': CORP violation (report only)
// 'NAV': COEP mismatch between the frames.
// 'NAV-RO': COEP mismatch between the frames (report only).
const reportingTest = function(
parent_headers, target_headers, expected_reports) {
// These tests are very slow, so they must be run in parallel using
// async_test.
promise_test_parallel(async t => {
const targetUrl = REMOTE_FRAME_URL + target_headers;
const parent = await loadFrames(t, parent_headers, targetUrl);
const contextUrl = parent.src ? parent.src : 'about:blank';
const reports = await observeReports(parent.contentWindow);
assert_equals(reports.length, expected_reports.length);
for (let i = 0; i < reports.length; i += 1) {
const report = reports[i];
switch (expected_reports[i]) {
case 'CORP':
checkCorpReport(report, contextUrl, targetUrl, 'enforce');
break;
case 'CORP-RO':
checkCorpReport(report, contextUrl, targetUrl, 'reporting');
break;
case 'NAV':
checkCoepMismatchReport(report, contextUrl, targetUrl, 'enforce');
break;
case 'NAV-RO':
checkCoepMismatchReport(report, contextUrl, targetUrl, 'reporting');
break;
default:
assert_unreached(
'Unexpected report exception: ' + expected_reports[i]);
}
}
}, `parent: ${desc(parent_headers)}, target: ${desc(target_headers)}, `);
}
reportingTest('', '', []);
reportingTest('', COEP, []);
reportingTest(COEP, COEP, ['CORP']);
reportingTest(COEP, '', ['CORP']);
reportingTest('', CORP_CROSS_ORIGIN, []);
reportingTest(COEP, CORP_CROSS_ORIGIN, ['NAV']);
reportingTest('', COEP + CORP_CROSS_ORIGIN, []);
reportingTest(COEP, COEP + CORP_CROSS_ORIGIN, []);
reportingTest(COEP_RO, COEP, ['CORP-RO']);
reportingTest(COEP_RO, '', ['CORP-RO', 'NAV-RO']);
reportingTest(COEP_RO, CORP_CROSS_ORIGIN, ['NAV-RO']);
reportingTest(COEP_RO, COEP + CORP_CROSS_ORIGIN, []);
reportingTest(COEP, COEP_RO + CORP_CROSS_ORIGIN, ['NAV']);
</script>
</body></html>

@ -0,0 +1,202 @@
<!doctype html>
<html>
<meta name="timeout" content="long">
<body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script>
const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
const BASE = "/html/cross-origin-embedder-policy/resources";
const FRAME_URL = `${ORIGIN}/common/blank.html` +
'?pipe=header(cross-origin-embedder-policy,credentialless)' +
`|header(cross-origin-embedder-policy-report-only,credentialless)`;
const WORKER_URL = `${ORIGIN}${BASE}/reporting-worker.js` +
'?pipe=header(cross-origin-embedder-policy,credentialless)' +
`|header(cross-origin-embedder-policy-report-only,credentialless)`;
const REPORTING_FRAME_URL = `${ORIGIN}${BASE}/reporting-empty-frame.html` +
'?pipe=header(cross-origin-embedder-policy,credentialless)' +
`|header(cross-origin-embedder-policy-report-only,credentialless)`;
function wait(ms) {
return new Promise(resolve => step_timeout(resolve, ms));
}
async function fetchInFrame(t, frameUrl, url) {
const reports = [];
const frame = await with_iframe(frameUrl);
t.add_cleanup(() => frame.remove());
const observer = new frame.contentWindow.ReportingObserver((rs) => {
for (const report of rs) {
reports.push(report.toJSON());
}
});
observer.observe();
const init = { mode: 'no-cors', cache: 'no-store' };
await frame.contentWindow.fetch(url, init).catch(() => {});
// Wait 1000ms for reports to settle.
await new Promise(r => step_timeout(r, 1000));
return reports;
}
async function fetchInWorker(workerOrPort, url) {
const script =
`fetch('${url}', {mode: 'no-cors', cache: 'no-store'}).catch(() => {});`;
const mc = new MessageChannel();
workerOrPort.postMessage({script, port: mc.port2}, [mc.port2]);
return (await new Promise(r => mc.port1.onmessage = r)).data;
}
function checkReport(report, contextUrl, blockedUrl, disposition, destination) {
assert_equals(report.type, 'coep');
assert_equals(report.url, contextUrl);
assert_equals(report.body.type, 'corp');
assert_equals(report.body.blockedURL, blockedUrl);
assert_equals(report.body.disposition, disposition);
assert_equals(report.body.destination, destination);
}
// We want to test several URLs in various environments (document,
// dedicated worker, shared worker, service worker). As expectations
// are independent of environment except for the context URLs in reports,
// we define ENVIRONMENTS and CASES to reduce the code duplication.
//
// ENVIRONMENTS is a list of dictionaries. Each dictionary consists of:
// - tag: the name of the environment
// - contextUrl: the URL of the environment settings object
// - run: an async function which generates reports
// - test: a testharness Test object
// - url: the URL for a test case (see below)
//
// CASES is a list of test cases. Each test case consists of:
// - name: the name of the test case
// - url: the URL of the test case
// - check: a function to check the results
// - reports: the generated reports
// - url: the URL of the test case
// - contextUrl: the URL of the environment settings object (see
// ENVORONMENTS)
const ENVIRONMENTS = [{
tag: 'document',
contextUrl: FRAME_URL,
run: async (test, url) => {
return await fetchInFrame(test, FRAME_URL, url);
},
}, {
tag: 'dedicated worker',
contextUrl: WORKER_URL,
run: async (test, url) => {
const worker = new Worker(WORKER_URL);
worker.addEventListener('error', test.unreached_func('Worker.onerror'));
test.add_cleanup(() => worker.terminate());
return await fetchInWorker(worker, url);
},
}, {
tag: 'shared worker',
contextUrl: WORKER_URL,
run: async (test, url) => {
const worker = new SharedWorker(WORKER_URL);
worker.addEventListener('error', test.unreached_func('Worker.onerror'));
return await fetchInWorker(worker.port, url);
},
}, {
tag: 'service worker',
contextUrl: WORKER_URL,
run: async (test, url) => {
// Generate a one-time scope for service workeer.
const SCOPE = `${BASE}/${token()}.html`;
const reg =
await service_worker_unregister_and_register(test, WORKER_URL, SCOPE);
test.add_cleanup(() => reg.unregister());
const worker = reg.installing || reg.waiting || reg.active;
worker.addEventListener('error', test.unreached_func('Worker.onerror'));
return await fetchInWorker(worker, url);
},
}, {
tag: 'between service worker and page',
contextUrl: REPORTING_FRAME_URL,
run: async (test, url) => {
// Service Worker without COEP.
const WORKER_URL = `${ORIGIN}${BASE}/sw.js`;
const reg = await service_worker_unregister_and_register(
test, WORKER_URL, REPORTING_FRAME_URL);
test.add_cleanup(() => reg.unregister());
const worker = reg.installing || reg.waiting || reg.active;
worker.addEventListener('error', test.unreached_func('Worker.onerror'));
return await fetchInFrame(
test, REPORTING_FRAME_URL, url);
},
}];
const CASES = [{
name: 'same-origin',
url: '/common/text-plain.txt',
check: (reports, url, contextUrl) => {
assert_equals(reports.length, 0);
}
}, {
name: 'blocked by CORP: same-origin',
url: `${REMOTE_ORIGIN}${BASE}/nothing-same-origin-corp.txt`,
check: (reports, url, contextUrl) => {
assert_equals(reports.length, 0);
}
}, {
name: 'blocked due to COEP',
url: `${REMOTE_ORIGIN}/common/text-plain.txt`,
check: (reports, contextUrl, url) => {
assert_equals(reports.length, 0);
}
}, {
name: 'blocked during redirect',
url: `${ORIGIN}/common/redirect.py?location=` +
encodeURIComponent(`${REMOTE_ORIGIN}/common/text-plain.txt`),
check: (reports, contextUrl, url) => {
// The redirection is blocked because CORP is required on the response here
// according to https://github.com/w3c/ServiceWorker/issues/1592
if (contextUrl === REPORTING_FRAME_URL) {
assert_equals(reports.length, 1);
checkReport(reports[0], contextUrl, url, 'enforce', '');
} else {
assert_equals(reports.length, 0);
}
},
}];
for (const env of ENVIRONMENTS) {
for (const testcase of CASES) {
promise_test(async (t) => {
const reports = await env.run(t, testcase.url);
testcase.check(reports, env.contextUrl, testcase.url);
}, `[${env.tag}] ${testcase.name}`);
}
}
// A test for a non-empty destination.
promise_test(async (t) => {
const reports = [];
const frame = await with_iframe(FRAME_URL);
t.add_cleanup(() => frame.remove());
const observer = new frame.contentWindow.ReportingObserver((rs) => {
for (const report of rs) {
reports.push(report.toJSON());
}
});
observer.observe();
const url = `${REMOTE_ORIGIN}/common/utils.js`;
const script = frame.contentDocument.createElement('script');
script.src = url;
frame.contentDocument.body.appendChild(script);
// Wait 200ms for reports to settle.
await t.step_timeout(200);
assert_equals(reports.length, 0);
}, 'destination: script');
</script>

@ -16,6 +16,9 @@ const FRAME_URL = `${ORIGIN}/common/blank.html` +
const WORKER_URL = `${ORIGIN}${BASE}/reporting-worker.js` +
'?pipe=header(cross-origin-embedder-policy,require-corp)' +
`|header(cross-origin-embedder-policy-report-only,require-corp)`;
const REPORTING_FRAME_URL = `${ORIGIN}${BASE}/reporting-empty-frame.html` +
'?pipe=header(cross-origin-embedder-policy,require-corp)' +
`|header(cross-origin-embedder-policy-report-only,require-corp)`;
function wait(ms) {
return new Promise(resolve => step_timeout(resolve, ms));
@ -117,18 +120,17 @@ const ENVIRONMENTS = [{
},
}, {
tag: 'between service worker and page',
contextUrl: `${ORIGIN}${BASE}/reporting-empty-frame.html`,
contextUrl: REPORTING_FRAME_URL,
run: async (test, url) => {
const SCOPE = `${BASE}/reporting-empty-frame.html`;
// Here we use a Service Worker without COEP.
const WORKER_URL = `${ORIGIN}${BASE}/sw.js`;
const reg =
await service_worker_unregister_and_register(test, WORKER_URL, SCOPE);
const reg = await service_worker_unregister_and_register(
test, WORKER_URL, REPORTING_FRAME_URL);
test.add_cleanup(() => reg.unregister());
const worker = reg.installing || reg.waiting || reg.active;
worker.addEventListener('error', test.unreached_func('Worker.onerror'));
return await fetchInFrame(
test, `${ORIGIN}${BASE}/reporting-empty-frame.html`, url);
test, REPORTING_FRAME_URL, url);
},
}];

@ -21,6 +21,9 @@
// .
const { REMOTE_ORIGIN } = get_host_info();
const BASE = new URL("resources", location).pathname
const FRAME_URL = `resources/reporting-empty-frame.html` +
`?pipe=header(cross-origin-embedder-policy,require-corp;report-to="endpoint")` +
`|header(cross-origin-embedder-policy-report-only,require-corp;report-to="report-only-endpoint")`;
function wait(ms) {
return new Promise(resolve => step_timeout(resolve, ms));
@ -65,7 +68,6 @@ async function checkNavigationReportExistence(endpoint, blockedUrl, contextUrl,
const retryDelay = 200;
for (let i = 0; i * retryDelay < timeout; i++) {
const reports = await fetchReports(endpoint);
for (const report of reports) {
if (report.type !== 'coep' || report.url !== contextUrl ||
report.body.type !== 'navigation') {
@ -85,7 +87,7 @@ promise_test(async t => {
const iframe = document.createElement('iframe');
t.add_cleanup(() => iframe.remove());
iframe.src = `resources/reporting-empty-frame.html`
iframe.src = FRAME_URL
document.body.appendChild(iframe);
await new Promise(resolve => {
iframe.addEventListener('load', resolve, {once: true});
@ -106,7 +108,7 @@ promise_test(async t => {
const iframe = document.createElement('iframe');
t.add_cleanup(() => iframe.remove());
iframe.src = `resources/reporting-empty-frame.html`
iframe.src = FRAME_URL
document.body.appendChild(iframe);
await new Promise(resolve => {
iframe.addEventListener('load', resolve, {once: true});
@ -135,7 +137,7 @@ promise_test(async (t) => {
const iframe = document.createElement('iframe');
t.add_cleanup(() => iframe.remove());
iframe.src = 'resources/reporting-empty-frame.html';
iframe.src = FRAME_URL;
const targetUrl = `/common/blank.html?${token()}`;
iframe.addEventListener('load', t.step_func(() => {
const nested = iframe.contentDocument.createElement('iframe');

@ -1,2 +0,0 @@
cross-origin-embedder-policy: require-corp; report-to="endpoint"
cross-origin-embedder-policy-report-only: require-corp; report-to="report-only-endpoint"