tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

early-hints-helpers.sub.js (7094B)


      1 "use strict";
      2 
      3 const SAME_ORIGIN = "https://{{host}}:{{ports[h2][0]}}";
      4 const CROSS_ORIGIN = "https://{{hosts[alt][www]}}:{{ports[h2][0]}}";
      5 
      6 const RESOURCES_PATH = "/loading/early-hints/resources";
      7 const SAME_ORIGIN_RESOURCES_URL = SAME_ORIGIN + RESOURCES_PATH;
      8 const CROSS_ORIGIN_RESOURCES_URL = CROSS_ORIGIN + RESOURCES_PATH;
      9 
     10 /**
     11 * Navigate to a test page with an Early Hints response.
     12 *
     13 * @typedef {Object} Preload
     14 * @property {string} url - A URL to preload. Note: This is relative to the
     15 *     `test_url` parameter of `navigateToTestWithEarlyHints()`.
     16 * @property {string} as_attr - `as` attribute of this preload.
     17 * @property {string} [crossorigin_attr] - `crossorigin` attribute of this
     18 *     preload.
     19 * @property {string} [fetchpriority_attr] - `fetchpriority` attribute of this
     20 *     preload.
     21 *
     22 * @param {string} test_url - URL of a test after the Early Hints response.
     23 * @param {Array<Preload>} preloads  - Preloads included in the Early Hints response.
     24 * @param {bool} exclude_preloads_from_ok_response - Whether to exclude the preloads from the 200 OK reponse.
     25 */
     26 function navigateToTestWithEarlyHints(test_url, preloads, exclude_preloads_from_ok_response) {
     27    const params = new URLSearchParams();
     28    params.set("test_url", test_url);
     29    params.set("exclude_preloads_from_ok_response",
     30               (!!exclude_preloads_from_ok_response).toString());
     31    for (const preload of preloads) {
     32        params.append("preloads", JSON.stringify(preload));
     33    }
     34    const url = RESOURCES_PATH +"/early-hints-test-loader.h2.py?" + params.toString();
     35    window.location.replace(new URL(url, window.location));
     36 }
     37 
     38 /**
     39 * Parses the query string of the current window location and returns preloads
     40 * in the Early Hints response sent via `navigateToTestWithEarlyHints()`.
     41 *
     42 * @returns {Array<Preload>}
     43 */
     44 function getPreloadsFromSearchParams() {
     45    const params = new URLSearchParams(window.location.search);
     46    const encoded_preloads = params.getAll("preloads");
     47    const preloads = [];
     48    for (const encoded of encoded_preloads) {
     49        preloads.push(JSON.parse(encoded));
     50    }
     51    return preloads;
     52 }
     53 
     54 /**
     55 * Fetches a script or an image.
     56 *
     57 * @param {string} element - "script" or "img".
     58 * @param {string} url - URL of the resource.
     59 */
     60 async function fetchResource(element, url) {
     61    return new Promise((resolve, reject) => {
     62        const el = document.createElement(element);
     63        el.src = url;
     64        el.onload = resolve;
     65        el.onerror = _ => reject(new Error("Failed to fetch resource: " + url));
     66        document.body.appendChild(el);
     67    });
     68 }
     69 
     70 /**
     71 * Fetches a script.
     72 *
     73 * @param {string} url
     74 */
     75 async function fetchScript(url) {
     76    return fetchResource("script", url);
     77 }
     78 
     79 /**
     80 * Fetches an image.
     81 *
     82 * @param {string} url
     83 */
     84 async function fetchImage(url) {
     85    return fetchResource("img", url);
     86 }
     87 
     88 /**
     89 * Returns true when the resource is preloaded via Early Hints.
     90 *
     91 * @param {string} url
     92 * @returns {boolean}
     93 */
     94 function isPreloadedByEarlyHints(url) {
     95    const entries = performance.getEntriesByName(url);
     96    if (entries.length === 0) {
     97        return false;
     98    }
     99    assert_equals(entries.length, 1);
    100    return entries[0].initiatorType === "early-hints";
    101 }
    102 
    103 /**
    104 * Navigate to the referrer policy test page.
    105 *
    106 * @param {string} referrer_policy - A value of Referrer-Policy to test.
    107 */
    108 function testReferrerPolicy(referrer_policy) {
    109    const params = new URLSearchParams();
    110    params.set("referrer-policy", referrer_policy);
    111    const same_origin_preload_url = SAME_ORIGIN_RESOURCES_URL + "/fetch-and-record-js.h2.py?id=" + token();
    112    params.set("same-origin-preload-url", same_origin_preload_url);
    113    const cross_origin_preload_url = CROSS_ORIGIN_RESOURCES_URL + "/fetch-and-record-js.h2.py?id=" + token();
    114    params.set("cross-origin-preload-url", cross_origin_preload_url);
    115 
    116    const path = "resources/referrer-policy-test-loader.h2.py?" + params.toString();
    117    const url = new URL(path, window.location);
    118    window.location.replace(url);
    119 }
    120 
    121 /**
    122 * Navigate to the content security policy basic test. The test page sends an
    123 * Early Hints response with a cross origin resource preload. CSP headers are
    124 * configured based on the given policies. A policy should be one of the
    125 * followings:
    126 *   "absent" - Do not send Content-Security-Policy header
    127 *   "allowed" - Set Content-Security-Policy to allow the cross origin preload
    128 *   "disallowed" - Set Content-Security-Policy to disallow the cross origin  preload
    129 *
    130 * @param {string} early_hints_policy - The policy for the Early Hints response
    131 * @param {string} final_policy - The policy for the final response
    132 */
    133 function navigateToContentSecurityPolicyBasicTest(
    134    early_hints_policy, final_policy) {
    135    const params = new URLSearchParams();
    136    params.set("resource-origin", CROSS_ORIGIN);
    137    params.set("resource-url",
    138        CROSS_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
    139    params.set("early-hints-policy", early_hints_policy);
    140    params.set("final-policy", final_policy);
    141 
    142    const url = "resources/csp-basic-loader.h2.py?" + params.toString();
    143    window.location.replace(new URL(url, window.location));
    144 }
    145 
    146 /**
    147 * Navigate to a test page which sends an Early Hints containing a cross origin
    148 * preload link with/without Content-Security-Policy header. The CSP header is
    149 * configured based on the given policy. The test page disallows the preload
    150 * while the preload is in-flight. The policy should be one of the followings:
    151 *   "absent" - Do not send Content-Security-Policy header
    152 *   "allowed" - Set Content-Security-Policy to allow the cross origin preload
    153 *
    154 * @param {string} early_hints_policy
    155 */
    156 function navigateToContentSecurityPolicyDocumentDisallowTest(early_hints_policy) {
    157    const resource_id = token();
    158    const params = new URLSearchParams();
    159    params.set("resource-origin", CROSS_ORIGIN);
    160    params.set("resource-url",
    161        CROSS_ORIGIN_RESOURCES_URL + "/delayed-js.h2.py?id=" + resource_id);
    162    params.set("resume-url",
    163        CROSS_ORIGIN_RESOURCES_URL + "/resume-delayed-js.h2.py?id=" + resource_id);
    164    params.set("early-hints-policy", early_hints_policy);
    165 
    166    const url = "resources/csp-document-disallow-loader.h2.py?" + params.toString();
    167    window.location.replace(new URL(url, window.location));
    168 }
    169 
    170 /**
    171 * Navigate to a test page which sends different Cross-Origin-Embedder-Policy
    172 * values in an Early Hints response and the final response.
    173 *
    174 * @param {string} early_hints_policy - The policy for the Early Hints response
    175 * @param {string} final_policy - The policy for the final response
    176 */
    177 function navigateToCrossOriginEmbedderPolicyMismatchTest(
    178    early_hints_policy, final_policy) {
    179    const params = new URLSearchParams();
    180    params.set("resource-url",
    181        CROSS_ORIGIN_RESOURCES_URL + "/empty-corp-absent.js?" + token());
    182    params.set("early-hints-policy", early_hints_policy);
    183    params.set("final-policy", final_policy);
    184 
    185    const url = "resources/coep-mismatch.h2.py?" + params.toString();
    186    window.location.replace(new URL(url, window.location));
    187 }