blob: 8e1631cb79ba71ee8091e505be067be96a279779 [file] [log] [blame]
<!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>