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]; }, };