Skip to content

Commit

Permalink
Fetch: connection pool partitioning tests
Browse files Browse the repository at this point in the history
  • Loading branch information
MattMenke2 committed Sep 14, 2020
1 parent 6a54c54 commit df8d82e
Show file tree
Hide file tree
Showing 9 changed files with 526 additions and 0 deletions.
264 changes: 264 additions & 0 deletions fetch/connection-pool/network-partition-key.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
<!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>
30 changes: 30 additions & 0 deletions fetch/connection-pool/resources/network-partition-checker.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Network Partition Checker</title>
<meta name="help" href="https://fetch.spec.whatwg.org/#network-partition-keys">
<meta name="timeout" content="normal">
<script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=common/utils.js"></script>
<script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=resources/testharness.js"></script>
<script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=fetch/connection-pool/resources/network-partition-key.js"></script>
</head>
<body>
<script>
async function fetch_and_reply() {
// If this is a top level window, report to the opener. Otherwise, this is an iframe,
// so report to the parent.
var report_to = window.opener;
if (!report_to)
report_to = window.parent;
try {
await check_partition_ids();
report_to.postMessage({result: 'success'}, '*');
} catch (e) {
report_to.postMessage({result: 'error', details: e.message}, '*');
}
}
fetch_and_reply();
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Iframe Network Partition Checker</title>
<meta name="help" href="https://fetch.spec.whatwg.org/#network-partition-keys">
<meta name="timeout" content="normal">
<script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=common/utils.js"></script>
<script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=resources/testharness.js"></script>
<script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=fetch/connection-pool/resources/network-partition-key.js"></script>
</head>
<body>
<script>
// Listen for first message from the iframe, and pass it back to the opener.
function message_listener(event) {
window.opener.postMessage(event.data, '*');
}
window.addEventListener('message', message_listener, {once: 'true'});
</script>
<iframe src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=fetch/connection-pool/resources/network-partition-checker.html"></iframe>
</body>
</html>
47 changes: 47 additions & 0 deletions fetch/connection-pool/resources/network-partition-key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Runs multiple fetches that validate connections see only a single partition_id.
// Requests are run in parallel so that they use multiple connections to maximize the
// chance of exercising all matching connections in the connection pool. Only returns
// once all requests have completed to make cleaning up server state non-racy.
function check_partition_ids(location) {
const NUM_FETCHES = 20;

var base_url = 'SUBRESOURCE_PREFIX:&dispatch=check_partition';

// Not a perfect parse of the query string, but good enough for this test.
var include_credentials = base_url.search('include_credentials=true') != -1;
var exclude_credentials = base_url.search('include_credentials=false') != -1;
if (include_credentials != !exclude_credentials)
throw new Exception('Credentials mode not specified');


// Run NUM_FETCHES in parallel.
var fetches = [];
for (i = 0; i < NUM_FETCHES; ++i) {
var fetch_params = {
credentials: 'omit',
mode: 'cors',
headers: {
'Header-To-Force-CORS': 'cors'
},
};

// Use a unique URL for each request, in case the caching layer serializes multiple
// requests for the same URL.
var url = `${base_url}&${token()}`;

fetches.push(fetch(url, fetch_params).then(
function (response) {
return response.text().then(function(text) {
assert_equals(text, 'ok', `Socket unexpectedly reused`);
});
}));
}

// Wait for all promises to complete.
return Promise.allSettled(fetches).then(function (results) {
results.forEach(function (result) {
if (result.status != 'fulfilled')
throw result.reason;
});
});
}

0 comments on commit df8d82e

Please sign in to comment.