1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
|
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];
},
};
|