<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Connection partitioning by site</title>
<meta name="help" href="https://fetch.spec.whatwg.org/#network-partition-keys">
<meta name="timeout" content="long">
<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>
</head>
<body>
<!-- Used to open about:blank tabs from opaque origins -->
<iframe id="iframe0" sandbox="allow-popups allow-scripts allow-popups-to-escape-sandbox"></iframe>
<iframe id="iframe1" sandbox="allow-popups allow-scripts allow-popups-to-escape-sandbox"></iframe>
<script>
const host = get_host_info();
// These two origins must correspond to different sites for this test to pass.
const POPUP_ORIGINS = [
host.ORIGIN,
host.HTTP_NOTSAMESITE_ORIGIN
];
// This origin should ideally correspond to a different site from the two above, but the
// tests will still pass if it matches the site of one of the other two origins.
const OTHER_ORIGIN = host.REMOTE_ORIGIN;
// Except for the csp_sandbox and about:blanks, each test opens up two windows, one at
// POPUP_ORIGINS[0], one at POPUP_ORIGINS[1], and has them request subresources from
// subresource_origin. All requests (HTML, JS, and fetch requests) for each window go
// through network-partition-key.py and have a partition_id parameter, which is used
// to check if any request for one window uses the same socket as a request for the
// other window.
//
// Whenever requests from the two different popup windows use the same connection, the
// fetch requests all start returning 400 errors, but other requests will continue to
// succeed, to make for clearer errors.
//
// include_credentials indicates whether the fetch requests use credentials or not,
// which is interesting as uncredentialed sockets have separate connection pools.
const tests = [
{
name: 'With credentials',
subresource_origin: POPUP_ORIGINS[0],
include_credentials: true,
popup_params: [
{type: 'main_frame'},
{type: 'main_frame'}
]
},
{
name: 'Without credentials',
subresource_origin: POPUP_ORIGINS[0],
include_credentials: false,
popup_params: [
{type: 'main_frame'},
{type: 'main_frame'}
]
},
{
name: 'Cross-site resources with credentials',
subresource_origin: OTHER_ORIGIN,
include_credentials: true,
popup_params: [
{type: 'main_frame'},
{type: 'main_frame'}
]
},
{
name: 'Cross-site resources without credentials',
subresource_origin: OTHER_ORIGIN,
include_credentials: false,
popup_params: [
{type: 'main_frame'},
{type: 'main_frame'}
]
},
{
name: 'Iframes',
subresource_origin: OTHER_ORIGIN,
include_credentials: true,
popup_params: [
{
type: 'iframe',
iframe_origin: OTHER_ORIGIN
},
{
type: 'iframe',
iframe_origin: OTHER_ORIGIN
}
]
},
{
name: 'Workers',
subresource_origin: POPUP_ORIGINS[0],
include_credentials: true,
popup_params: [
{type: 'worker'},
{type: 'worker'}
]
},
{
name: 'Workers with cross-site resources',
subresource_origin: OTHER_ORIGIN,
include_credentials: true,
popup_params: [
{type: 'worker'},
{type: 'worker'}
]
},
{
name: 'CSP sandbox',
subresource_origin: POPUP_ORIGINS[0],
include_credentials: true,
popup_params: [
{type: 'csp_sandbox'},
{type: 'csp_sandbox'}
]
},
{
name: 'about:blank from opaque origin iframe',
subresource_origin: OTHER_ORIGIN,
include_credentials: true,
popup_params: [
{type: 'opaque_about_blank'},
{type: 'opaque_about_blank'}
]
},
];
const BASE_PATH = window.location.pathname.replace(/\/[^\/]*$/, '/');
function create_script_url(origin, uuid, partition_id, dispatch) {
return `${origin}${BASE_PATH}resources/network-partition-key.py?uuid=${uuid}&partition_id=${partition_id}&dispatch=${dispatch}`
}
function run_test(test) {
var uuid = token();
// Used to track the opened popup windows, so they can be closed at the end of the test.
// They could be closed immediately after use, but safest to keep them open, as browsers
// could use closing a window as a hint to close idle sockets that the window used.
var popup_windows = [];
// Creates a popup window at |url| and waits for a test result. Returns a promise.
function create_popup_and_wait_for_result(url) {
return new Promise(function(resolve, reject) {
popup_windows.push(window.open(url));
// Listen for the result
function message_listener(event) {
if (event.data.result === 'success') {
resolve();
} else if (event.data.result === 'error') {
reject(event.data.details);
} else {
reject('Unexpected message.');
}
}
window.addEventListener('message', message_listener, {once: 'true'});
});
}
// Navigates iframe to url and waits for a test result. Returns a promise.
function navigate_iframe_and_wait_for_result(iframe, url) {
return new Promise(function(resolve, reject) {
iframe.src = url;
// Listen for the result
function message_listener(event) {
if (event.data.result === 'success') {
resolve();
} else if (event.data.result === 'error') {
reject(event.data.details);
} else {
reject('Unexpected message.');
}
}
window.addEventListener('message', message_listener, {once: 'true'});
});
}
function make_test_function(test, index) {
var popup_params = test.popup_params[index];
return function() {
var popup_path;
var additional_url_params = '';
var origin = POPUP_ORIGINS[index];
var partition_id = POPUP_ORIGINS[index];
if (popup_params.type == 'main_frame') {
popup_path = 'resources/network-partition-checker.html';
} else if (popup_params.type == 'iframe') {
popup_path = 'resources/network-partition-iframe-checker.html';
additional_url_params = `&other_origin=${popup_params.iframe_origin}`;
} else if (popup_params.type == 'worker') {
popup_path = 'resources/network-partition-worker-checker.html';
// The origin of the dedicated worker must mutch the page that loads it.
additional_url_params = `&other_origin=${POPUP_ORIGINS[index]}`;
} else if (popup_params.type == 'csp_sandbox') {
// For the Content-Security-Policy sandbox test, all requests are from the same origin, but
// the origin should be treated as an opaque origin, so sockets should not be reused.
origin = test.subresource_origin;
partition_id = index;
popup_path = 'resources/network-partition-checker.html';
// Don't check partition of root document, since the document isn't sandboxed until the
// root document is fetched.
additional_url_params = '&sandbox=true&nocheck_partition=true'
} else if (popup_params.type=='opaque_about_blank') {
popup_path = 'resources/network-partition-about-blank-checker.html';
} else if (popup_params.type == 'iframe') {
throw 'Unrecognized popup_params.type.';
}
var url = create_script_url(origin, uuid, partition_id, 'fetch_file');
url += `&subresource_origin=${test.subresource_origin}`
url += `&include_credentials=${test.include_credentials}`
url += `&path=${BASE_PATH.substring(1)}${popup_path}`;
url += additional_url_params;
if (popup_params.type=='opaque_about_blank') {
return navigate_iframe_and_wait_for_result(iframe = document.getElementById('iframe' + index), url);
}
return create_popup_and_wait_for_result(url);
}
}
// Takes a Promise, and cleans up state when the promise has completed, successfully or not, re-throwing
// any exception from the passed in Promise.
async function clean_up_when_done(promise) {
var error;
try {
await promise;
} catch (e) {
error = e;
}
popup_windows.map(function (win) { win.close(); });
try {
var cleanup_url = create_script_url(host.ORIGIN, uuid, host.ORIGIN, 'clean_up');
var response = await fetch(cleanup_url, {credentials: 'omit', mode: 'cors'});
assert_equals(await response.text(), 'cleanup complete', `Sever state cleanup failed`);
} catch (e) {
// Prefer error from the passed in Promise over errors from the fetch request to clean up server state.
error = error || e;
}
if (error)
throw error;
}
return clean_up_when_done(
make_test_function(test, 0)()
.then(make_test_function(test, 1)));
}
tests.forEach(function (test) {
promise_test(
function() { return run_test(test); },
test.name);
})
</script>
</body>
</html>