Source code

Revision control

Other Tools

Test Info:

<!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 = new URL("resources", location).pathname
const FRAME_URL = `${ORIGIN}/common/blank.html` +
'?pipe=header(cross-origin-embedder-policy,require-corp)' +
`|header(cross-origin-embedder-policy-report-only,require-corp)`;
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)`;
async function observeReports(global, expected_count) {
const reports = [];
const receivedEveryReports = new Promise(resolve => {
if (expected_count == 0)
resolve();
const observer = new global.ReportingObserver((rs) => {
for (const r of rs) {
reports.push(r.toJSON());
}
if (expected_count <= reports.length)
resolve();
});
observer.observe();
});
await receivedEveryReports;
// Wait 500ms more to catch additionnal unexpected reports.
await new Promise(r => step_timeout(r, 500));
return reports;
}
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);
}
async function fetchInFrame(t, frameUrl, url, expected_count) {
const frame = await with_iframe(frameUrl);
t.add_cleanup(() => frame.remove());
const init = { mode: 'no-cors', cache: 'no-store' };
let future_reports = observeReports(frame.contentWindow, expected_count);
await frame.contentWindow.fetch(url, init).catch(() => {});
return await future_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;
}
// We want to test several URLs in various environments (document,
// dedicated worker, shared worser, 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, expected_count) => {
return await fetchInFrame(test, FRAME_URL, url, expected_count);
},
}, {
tag: 'dedicated worker',
contextUrl: WORKER_URL,
run: async (test, url, expected_count) => {
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, expected_count) => {
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, expected_count) => {
// As we don't want the service worker to control any page, generate a
// one-time scope.
const SCOPE = new URL(`resources/${token()}.html`, location).pathname;
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, expected_count) => {
// 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, 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, expected_count);
},
}];
const CASES = [{
name: 'same-origin',
url: '/common/text-plain.txt',
expected_count: 0,
check: (reports, url, contextUrl) => {}
}, {
name: 'blocked by CORP: same-origin',
url: `${REMOTE_ORIGIN}${BASE}/nothing-same-origin-corp.txt`,
expected_count: 0,
check: (reports, url, contextUrl) => {}
}, {
name: 'blocked due to COEP',
url: `${REMOTE_ORIGIN}/common/text-plain.txt`,
expected_count: 2,
check: (reports, contextUrl, url) => {
checkReport(reports[0], contextUrl, url, 'reporting', '');
checkReport(reports[1], contextUrl, url, 'enforce', '');
}
}, {
name: 'blocked during redirect',
url: `${ORIGIN}/common/redirect.py?location=` +
encodeURIComponent(`${REMOTE_ORIGIN}/common/text-plain.txt`),
expected_count: 2,
check: (reports, contextUrl, url) => {
checkReport(reports[0], contextUrl, url, 'reporting', '');
checkReport(reports[1], contextUrl, url, 'enforce', '');
},
}];
for (const env of ENVIRONMENTS) {
for (const testcase of CASES) {
promise_test(async (t) => {
const reports = await env.run(
t, testcase.url, testcase.expected_count);
assert_equals(reports.length, testcase.expected_count);
testcase.check(reports, env.contextUrl, testcase.url);
}, `[${env.tag}] ${testcase.name}`);
}
}
// A test for a non-empty destination.
promise_test(async (t) => {
const frame = await with_iframe(FRAME_URL);
t.add_cleanup(() => frame.remove());
const url = `${REMOTE_ORIGIN}/common/utils.js`;
const script = frame.contentDocument.createElement('script');
script.src = url;
const future_reports = observeReports(frame.contentWindow, 2);
frame.contentDocument.body.appendChild(script);
const reports = await future_reports;
assert_equals(reports.length, 2);
checkReport(reports[0], FRAME_URL, url, 'reporting', 'script');
checkReport(reports[1], FRAME_URL, url, 'enforce', 'script');
}, 'destination: script');
</script>