212 lines
7.7 KiB
JavaScript
212 lines
7.7 KiB
JavaScript
|
|
const executor_path = '/common/dispatcher/executor.html?pipe=';
|
|
const remote_executor_path = '/common/dispatcher/remote-executor.html?pipe=';
|
|
const executor_worker_path = '/common/dispatcher/executor-worker.js?pipe=';
|
|
const remote_executor_worker_path = '/common/dispatcher/remote-executor-worker.js?pipe=';
|
|
const executor_service_worker_path = '/common/dispatcher/executor-service-worker.js?pipe=';
|
|
|
|
// COEP
|
|
const coep_none =
|
|
'|header(Cross-Origin-Embedder-Policy,none)';
|
|
const coep_credentialless =
|
|
'|header(Cross-Origin-Embedder-Policy,credentialless)';
|
|
|
|
// DIP
|
|
const dip_none =
|
|
'|header(Document-Isolation-Policy,none)';
|
|
const dip_credentialless =
|
|
'|header(Document-Isolation-Policy,isolate-and-credentialless)';
|
|
const dip_require_corp =
|
|
'|header(Document-Isolation-Policy,isolate-and-require-corp)';
|
|
|
|
// DIP-Report-Only
|
|
const dip_report_only_credentialless =
|
|
'|header(Document-Isolation-Policy-Report-Only,isolate-and-credentialless)';
|
|
|
|
// CORP
|
|
const corp_cross_origin =
|
|
'|header(Cross-Origin-Resource-Policy,cross-origin)';
|
|
|
|
const cookie_same_site_none = ';SameSite=None;Secure';
|
|
|
|
// Test using the modern async/await primitives are easier to read/write.
|
|
// However they run sequentially, contrary to async_test. This is the parallel
|
|
// version, to avoid timing out.
|
|
let promise_test_parallel = (promise, description) => {
|
|
async_test(test => {
|
|
promise(test)
|
|
.then(() => test.done())
|
|
.catch(test.step_func(error => { throw error; }));
|
|
}, description);
|
|
};
|
|
|
|
// Add a cookie |cookie_key|=|cookie_value| on an |origin|.
|
|
// Note: cookies visibility depends on the path of the document. Those are set
|
|
// from a document from: /html/cross-origin-embedder-policy/credentialless/. So
|
|
// the cookie is visible to every path underneath.
|
|
const setCookie = async (origin, cookie_key, cookie_value) => {
|
|
const popup_token = token();
|
|
const popup_url = origin + executor_path + `&uuid=${popup_token}`;
|
|
const popup = window.open(popup_url);
|
|
|
|
const reply_token = token();
|
|
send(popup_token, `
|
|
document.cookie = "${cookie_key}=${cookie_value}";
|
|
send("${reply_token}", "done");
|
|
`);
|
|
assert_equals(await receive(reply_token), "done");
|
|
popup.close();
|
|
}
|
|
|
|
let parseCookies = function(headers_json) {
|
|
if (!headers_json["cookie"])
|
|
return {};
|
|
|
|
return headers_json["cookie"]
|
|
.split(';')
|
|
.map(v => v.split('='))
|
|
.reduce((acc, v) => {
|
|
acc[v[0].trim()] = v[1].trim();
|
|
return acc;
|
|
}, {});
|
|
}
|
|
|
|
// Open a new window with a given |origin|, loaded with DIP:credentialless. The
|
|
// new document will execute any scripts sent toward the token it returns.
|
|
const newCredentiallessWindow = (origin) => {
|
|
const main_document_token = token();
|
|
const url = origin + executor_path + dip_credentialless +
|
|
`&uuid=${main_document_token}`;
|
|
const context = window.open(url);
|
|
add_completion_callback(() => w.close());
|
|
return main_document_token;
|
|
};
|
|
|
|
// Create a new iframe, loaded with DIP:credentialless.
|
|
// The new document will execute any scripts sent toward the token it returns.
|
|
const newCredentiallessIframe = (parent_token, child_origin) => {
|
|
const sub_document_token = token();
|
|
const iframe_url = child_origin + executor_path + dip_credentialless +
|
|
`&uuid=${sub_document_token}`;
|
|
send(parent_token, `
|
|
let iframe = document.createElement("iframe");
|
|
iframe.src = "${iframe_url}";
|
|
document.body.appendChild(iframe);
|
|
`)
|
|
return sub_document_token;
|
|
};
|
|
|
|
// The following functions create remote execution contexts with the matching
|
|
// origins and headers. The first return value is the uuid that can be used
|
|
// to instantiate a RemoteContext object. The second return value is the URL of
|
|
// the context that was created.
|
|
async function createIframeContext(t, origin, header) {
|
|
const uuid = token();
|
|
const frame_url = origin + remote_executor_path + header + '&uuid=' + uuid;
|
|
const frame = await with_iframe(frame_url);
|
|
t.add_cleanup(() => frame.remove());
|
|
return [uuid, frame_url];
|
|
}
|
|
|
|
async function createDedicatedWorkerContext(t, origin, header) {
|
|
const iframe_uuid = token();
|
|
const frame_url = origin + remote_executor_path + header + '&uuid=' + iframe_uuid;
|
|
const frame = await with_iframe(frame_url);
|
|
t.add_cleanup(() => frame.remove());
|
|
|
|
const uuid = token();
|
|
const worker_url = origin + remote_executor_worker_path + '&uuid=' + uuid;
|
|
const ctx = new RemoteContext(iframe_uuid);
|
|
await ctx.execute_script(
|
|
(url) => {
|
|
const worker = new Worker(url);
|
|
}, [worker_url]);
|
|
return [uuid, worker_url];
|
|
}
|
|
|
|
async function createSharedWorkerContext(t, origin, header) {
|
|
const uuid = token();
|
|
const worker_url = origin + remote_executor_worker_path + header + '&uuid=' + uuid;
|
|
const worker = new SharedWorker(worker_url);
|
|
worker.addEventListener('error', t.unreached_func('Worker.onerror'));
|
|
return [uuid, worker_url];
|
|
}
|
|
|
|
async function createIframeWithSWContext(t, origin, header) {
|
|
// Register a service worker with no headers.
|
|
const uuid = token();
|
|
const frame_url = origin + remote_executor_path + header + '&uuid=' + uuid;
|
|
const service_worker_url = origin + executor_service_worker_path;
|
|
const reg = await service_worker_unregister_and_register(
|
|
t, service_worker_url, frame_url);
|
|
const worker = reg.installing || reg.waiting || reg.active;
|
|
worker.addEventListener('error', t.unreached_func('Worker.onerror'));
|
|
|
|
const frame = await with_iframe(frame_url);
|
|
t.add_cleanup(() => {
|
|
reg.unregister();
|
|
frame.remove();
|
|
});
|
|
return [uuid, frame_url];
|
|
}
|
|
|
|
// A common interface for building the 4 type of execution contexts. Outputs the
|
|
// token needed to create the RemoteContext.
|
|
async function getTokenFromEnvironment(t, environment, headers) {
|
|
switch(environment) {
|
|
case "document":
|
|
const iframe_context = await createIframeContext(t, window.origin, headers);
|
|
return iframe_context[0];
|
|
case "dedicated_worker":
|
|
const dedicated_worker_context = await createDedicatedWorkerContext(t, window.origin, headers);
|
|
return dedicated_worker_context[0];
|
|
case "shared_worker":
|
|
const shared_worker_context = await createSharedWorkerContext(t, window.origin, headers);
|
|
return shared_worker_context[0];
|
|
case "service_worker":
|
|
const sw_context = await createIframeWithSWContext(t, window.origin, headers);
|
|
return sw_context[0];
|
|
}
|
|
}
|
|
|
|
// A common interface for building the 4 type of execution contexts:
|
|
// It outputs: [
|
|
// - The token to communicate with the environment.
|
|
// - A promise resolved when the environment encounters an error.
|
|
// ]
|
|
const environments = {
|
|
document: headers => {
|
|
const tok = token();
|
|
const url = window.origin + executor_path + headers + `&uuid=${tok}`;
|
|
const context = window.open(url);
|
|
add_completion_callback(() => context.close());
|
|
return [tok, new Promise(resolve => {})];
|
|
},
|
|
|
|
dedicated_worker: headers => {
|
|
const tok = token();
|
|
const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
|
|
const context = new Worker(url);
|
|
return [tok, new Promise(resolve => context.onerror = resolve)];
|
|
},
|
|
|
|
shared_worker: headers => {
|
|
const tok = token();
|
|
const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
|
|
const context = new SharedWorker(url);
|
|
return [tok, new Promise(resolve => context.onerror = resolve)];
|
|
},
|
|
|
|
service_worker: headers => {
|
|
const tok = token();
|
|
const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
|
|
const scope = url; // Generate a one-time scope for service worker.
|
|
const error = new Promise(resolve => {
|
|
navigator.serviceWorker.register(url, {scope: scope})
|
|
.then(registration => {
|
|
add_completion_callback(() => registration.unregister());
|
|
}, /* catch */ resolve);
|
|
});
|
|
return [tok, error];
|
|
},
|
|
};
|