| /** |
| * Utilities for initiating prefetch via speculation rules. |
| */ |
| |
| // Resolved URL to find this script. |
| const SR_PREFETCH_UTILS_URL = new URL(document.currentScript.src, document.baseURI); |
| // Hostname for cross origin urls. |
| const PREFETCH_PROXY_BYPASS_HOST = "{{hosts[alt][]}}"; |
| |
| class PrefetchAgent extends RemoteContext { |
| constructor(uuid, t) { |
| super(uuid); |
| this.t = t; |
| } |
| |
| getExecutorURL(options = {}) { |
| let {hostname, protocol, executor, ...extra} = options; |
| let params = new URLSearchParams({uuid: this.context_id, ...extra}); |
| if(executor === undefined) { |
| executor = "executor.sub.html"; |
| } |
| let url = new URL(`${executor}?${params}`, SR_PREFETCH_UTILS_URL); |
| if(hostname !== undefined) { |
| url.hostname = hostname; |
| } |
| if(protocol !== undefined) { |
| url.protocol = protocol; |
| url.port = protocol === "https" ? "{{ports[https][0]}}" : "{{ports[http][0]}}"; |
| } |
| return url; |
| } |
| |
| // Requests prefetch via speculation rules. |
| // |
| // In the future, this should also use browser hooks to force the prefetch to |
| // occur despite heuristic matching, etc., and await the completion of the |
| // prefetch. |
| async forceSinglePrefetch(url, extra = {}) { |
| await this.execute_script((url, extra) => { |
| insertSpeculationRules({ prefetch: [{source: 'list', urls: [url], ...extra}] }); |
| }, [url, extra]); |
| return new Promise(resolve => this.t.step_timeout(resolve, 2000)); |
| } |
| |
| async navigate(url) { |
| await this.execute_script((url) => { |
| window.executor.suspend(() => { |
| location.href = url; |
| }); |
| }, [url]); |
| assert_equals( |
| await this.execute_script(() => location.href), |
| url.toString(), |
| "expected navigation to reach destination URL"); |
| await this.execute_script(() => {}); |
| } |
| |
| async getRequestHeaders() { |
| return this.execute_script(() => requestHeaders); |
| } |
| |
| async getResponseCookies() { |
| return this.execute_script(() => { |
| let cookie = {}; |
| document.cookie.split(/\s*;\s*/).forEach((kv)=>{ |
| let [key, value] = kv.split(/\s*=\s*/); |
| cookie[key] = value; |
| }); |
| return cookie; |
| }); |
| } |
| |
| async getRequestCookies() { |
| return this.execute_script(() => window.requestCookies); |
| } |
| } |
| |
| // Produces n URLs with unique UUIDs which will record when they are prefetched. |
| function getPrefetchUrlList(n) { |
| let urls = []; |
| for (let i=0; i<n; i++) { |
| let params = new URLSearchParams({uuid: token()}); |
| urls.push(new URL(`prefetch.py?${params}`, SR_PREFETCH_UTILS_URL)); |
| } |
| return urls; |
| } |
| |
| function getRedirectUrl() { |
| let params = new URLSearchParams({uuid: token()}); |
| return new URL(`redirect.py?${params}`, SR_PREFETCH_UTILS_URL); |
| } |
| |
| async function isUrlPrefetched(url) { |
| let response = await fetch(url, {redirect: 'follow'}); |
| assert_true(response.ok); |
| return response.json(); |
| } |
| |
| // Must also include /common/utils.js and /common/dispatcher/dispatcher.js to use this. |
| async function spawnWindow(t, options = {}) { |
| let {executor, ...extra} = options; |
| let agent = new PrefetchAgent(token(), t); |
| let w = window.open(agent.getExecutorURL({executor}), extra); |
| t.add_cleanup(() => w.close()); |
| return agent; |
| } |
| |
| function insertSpeculationRules(body) { |
| let script = document.createElement('script'); |
| script.type = 'speculationrules'; |
| script.textContent = JSON.stringify(body); |
| document.head.appendChild(script); |
| } |
| |
| function assert_prefetched (requestHeaders, description) { |
| assert_in_array(requestHeaders.purpose, ["", "prefetch"], "The vendor-specific header Purpose, if present, must be 'prefetch'."); |
| assert_equals(requestHeaders.sec_purpose, "prefetch", description); |
| } |
| |
| function assert_not_prefetched (requestHeaders, description){ |
| assert_equals(requestHeaders.purpose, "", description); |
| assert_equals(requestHeaders.sec_purpose, "", description); |
| } |