common.js (7857B)
1 const executor_path = '/common/dispatcher/executor.html?pipe='; 2 const remote_executor_path = '/common/dispatcher/remote-executor.html?pipe='; 3 const executor_worker_path = '/common/dispatcher/executor-worker.js?pipe='; 4 const remote_executor_worker_path = '/common/dispatcher/remote-executor-worker.js?pipe='; 5 const executor_service_worker_path = '/common/dispatcher/executor-service-worker.js?pipe='; 6 7 // COEP 8 const coep_none = 9 '|header(Cross-Origin-Embedder-Policy,none)'; 10 const coep_credentialless = 11 '|header(Cross-Origin-Embedder-Policy,credentialless)'; 12 13 // DIP 14 const dip_none = 15 '|header(Document-Isolation-Policy,none)'; 16 const dip_credentialless = 17 '|header(Document-Isolation-Policy,isolate-and-credentialless)'; 18 const dip_require_corp = 19 '|header(Document-Isolation-Policy,isolate-and-require-corp)'; 20 21 // DIP-Report-Only 22 const dip_report_only_credentialless = 23 '|header(Document-Isolation-Policy-Report-Only,isolate-and-credentialless)'; 24 25 // CORP 26 const corp_cross_origin = 27 '|header(Cross-Origin-Resource-Policy,cross-origin)'; 28 29 const cookie_same_site_none = ';SameSite=None;Secure'; 30 31 // Test using the modern async/await primitives are easier to read/write. 32 // However they run sequentially, contrary to async_test. This is the parallel 33 // version, to avoid timing out. 34 let promise_test_parallel = (promise, description) => { 35 async_test(test => { 36 promise(test) 37 .then(() => test.done()) 38 .catch(test.step_func(error => { throw error; })); 39 }, description); 40 }; 41 42 // Add a cookie |cookie_key|=|cookie_value| on an |origin|. 43 // Note: cookies visibility depends on the path of the document. Those are set 44 // from a document from: /html/cross-origin-embedder-policy/credentialless/. So 45 // the cookie is visible to every path underneath. 46 const setCookie = async (origin, cookie_key, cookie_value) => { 47 const popup_token = token(); 48 const popup_url = origin + executor_path + `&uuid=${popup_token}`; 49 const popup = window.open(popup_url); 50 51 const reply_token = token(); 52 send(popup_token, ` 53 document.cookie = "${cookie_key}=${cookie_value}"; 54 send("${reply_token}", "done"); 55 `); 56 assert_equals(await receive(reply_token), "done"); 57 popup.close(); 58 } 59 60 let parseCookies = function(headers_json) { 61 if (!headers_json["cookie"]) 62 return {}; 63 64 return headers_json["cookie"] 65 .split(';') 66 .map(v => v.split('=')) 67 .reduce((acc, v) => { 68 acc[v[0].trim()] = v[1].trim(); 69 return acc; 70 }, {}); 71 } 72 73 // Open a new window with a given |origin|, loaded with DIP:credentialless. The 74 // new document will execute any scripts sent toward the token it returns. 75 const newCredentiallessWindow = (origin) => { 76 const main_document_token = token(); 77 const url = origin + executor_path + dip_credentialless + 78 `&uuid=${main_document_token}`; 79 const context = window.open(url); 80 add_completion_callback(() => w.close()); 81 return main_document_token; 82 }; 83 84 // Create a new iframe, loaded with DIP:credentialless. 85 // The new document will execute any scripts sent toward the token it returns. 86 const newCredentiallessIframe = (parent_token, child_origin) => { 87 const sub_document_token = token(); 88 const iframe_url = child_origin + executor_path + dip_credentialless + 89 `&uuid=${sub_document_token}`; 90 send(parent_token, ` 91 let iframe = document.createElement("iframe"); 92 iframe.src = "${iframe_url}"; 93 document.body.appendChild(iframe); 94 `) 95 return sub_document_token; 96 }; 97 98 // The following functions create remote execution contexts with the matching 99 // origins and headers. The first return value is the uuid that can be used 100 // to instantiate a RemoteContext object. The second return value is the URL of 101 // the context that was created. 102 async function createIframeContext(t, origin, header) { 103 const uuid = token(); 104 const frame_url = origin + remote_executor_path + header + '&uuid=' + uuid; 105 const frame = await with_iframe(frame_url); 106 t.add_cleanup(() => frame.remove()); 107 return [uuid, frame_url]; 108 } 109 110 async function createDedicatedWorkerContext(t, origin, header) { 111 const iframe_uuid = token(); 112 const frame_url = origin + remote_executor_path + header + '&uuid=' + iframe_uuid; 113 const frame = await with_iframe(frame_url); 114 t.add_cleanup(() => frame.remove()); 115 116 const uuid = token(); 117 const worker_url = origin + remote_executor_worker_path + '&uuid=' + uuid; 118 const ctx = new RemoteContext(iframe_uuid); 119 await ctx.execute_script( 120 (url) => { 121 const worker = new Worker(url); 122 }, [worker_url]); 123 return [uuid, worker_url]; 124 } 125 126 async function createSharedWorkerContext(t, origin, header) { 127 const uuid = token(); 128 const worker_url = origin + remote_executor_worker_path + header + '&uuid=' + uuid; 129 const worker = new SharedWorker(worker_url); 130 worker.addEventListener('error', t.unreached_func('Worker.onerror')); 131 return [uuid, worker_url]; 132 } 133 134 async function createIframeWithSWContext(t, origin, header) { 135 // Register a service worker with no headers. 136 const uuid = token(); 137 const frame_url = origin + remote_executor_path + header + '&uuid=' + uuid; 138 const service_worker_url = origin + executor_service_worker_path; 139 const reg = await service_worker_unregister_and_register( 140 t, service_worker_url, frame_url); 141 const worker = reg.installing || reg.waiting || reg.active; 142 worker.addEventListener('error', t.unreached_func('Worker.onerror')); 143 144 const frame = await with_iframe(frame_url); 145 t.add_cleanup(() => { 146 reg.unregister(); 147 frame.remove(); 148 }); 149 return [uuid, frame_url]; 150 } 151 152 // A common interface for building the 4 type of execution contexts. Outputs the 153 // token needed to create the RemoteContext. 154 async function getTokenFromEnvironment(t, environment, headers) { 155 switch(environment) { 156 case "document": 157 const iframe_context = await createIframeContext(t, window.origin, headers); 158 return iframe_context[0]; 159 case "dedicated_worker": 160 const dedicated_worker_context = await createDedicatedWorkerContext(t, window.origin, headers); 161 return dedicated_worker_context[0]; 162 case "shared_worker": 163 const shared_worker_context = await createSharedWorkerContext(t, window.origin, headers); 164 return shared_worker_context[0]; 165 case "service_worker": 166 const sw_context = await createIframeWithSWContext(t, window.origin, headers); 167 return sw_context[0]; 168 } 169 } 170 171 // A common interface for building the 4 type of execution contexts: 172 // It outputs: [ 173 // - The token to communicate with the environment. 174 // - A promise resolved when the environment encounters an error. 175 // ] 176 const environments = { 177 document: headers => { 178 const tok = token(); 179 const url = window.origin + executor_path + headers + `&uuid=${tok}`; 180 const context = window.open(url); 181 add_completion_callback(() => context.close()); 182 return [tok, new Promise(resolve => {})]; 183 }, 184 185 dedicated_worker: headers => { 186 const tok = token(); 187 const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`; 188 const context = new Worker(url); 189 return [tok, new Promise(resolve => context.onerror = resolve)]; 190 }, 191 192 shared_worker: headers => { 193 const tok = token(); 194 const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`; 195 const context = new SharedWorker(url); 196 return [tok, new Promise(resolve => context.onerror = resolve)]; 197 }, 198 199 service_worker: headers => { 200 const tok = token(); 201 const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`; 202 const scope = url; // Generate a one-time scope for service worker. 203 const error = new Promise(resolve => { 204 navigator.serviceWorker.register(url, {scope: scope}) 205 .then(registration => { 206 add_completion_callback(() => registration.unregister()); 207 }, /* catch */ resolve); 208 }); 209 return [tok, error]; 210 }, 211 };