tor-browser

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

test_fetch_cors.js (49808B)


      1 var path = "/tests/dom/base/test/";
      2 
      3 function isOpaqueResponse(response) {
      4  return (
      5    response.type == "opaque" &&
      6    response.status === 0 &&
      7    response.statusText === ""
      8  );
      9 }
     10 
     11 function testModeSameOrigin() {
     12  // Fetch spec Section 4, step 4, "request's mode is same-origin".
     13  var req = new Request("https://example.net", { mode: "same-origin" });
     14  return fetch(req).then(
     15    function (res) {
     16      ok(
     17        false,
     18        "Attempting to fetch a resource from a different origin with mode same-origin should fail."
     19      );
     20    },
     21    function (e) {
     22      ok(
     23        e instanceof TypeError,
     24        "Attempting to fetch a resource from a different origin with mode same-origin should fail."
     25      );
     26    }
     27  );
     28 }
     29 
     30 function testNoCorsCtor() {
     31  // Request constructor Step 19.1
     32  var simpleMethods = ["GET", "HEAD", "POST"];
     33  for (var i = 0; i < simpleMethods.length; ++i) {
     34    var r = new Request("http://example.com", {
     35      method: simpleMethods[i],
     36      mode: "no-cors",
     37    });
     38    ok(
     39      true,
     40      "no-cors Request with simple method " + simpleMethods[i] + " is allowed."
     41    );
     42  }
     43 
     44  var otherMethods = ["DELETE", "OPTIONS", "PUT"];
     45  for (var i = 0; i < otherMethods.length; ++i) {
     46    try {
     47      var r = new Request("http://example.com", {
     48        method: otherMethods[i],
     49        mode: "no-cors",
     50      });
     51      ok(
     52        false,
     53        "no-cors Request with non-simple method " +
     54          otherMethods[i] +
     55          " is not allowed."
     56      );
     57    } catch (e) {
     58      ok(
     59        true,
     60        "no-cors Request with non-simple method " +
     61          otherMethods[i] +
     62          " is not allowed."
     63      );
     64    }
     65  }
     66 
     67  // Request constructor Step 19.2, check guarded headers.
     68  var r = new Request(".", { mode: "no-cors" });
     69  r.headers.append("Content-Type", "multipart/form-data");
     70  is(
     71    r.headers.get("content-type"),
     72    "multipart/form-data",
     73    "Appending simple header should succeed"
     74  );
     75  r.headers.append("custom", "value");
     76  ok(!r.headers.has("custom"), "Appending custom header should fail");
     77  r.headers.append("DNT", "value");
     78  ok(!r.headers.has("DNT"), "Appending forbidden header should fail");
     79 }
     80 
     81 var corsServerPath =
     82  "/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?";
     83 function testModeNoCors() {
     84  // Fetch spec, section 4, step 4, response tainting should be set opaque, so
     85  // that fetching leads to an opaque filtered response in step 8.
     86  var r = new Request("https://example.net" + corsServerPath + "status=200", {
     87    mode: "no-cors",
     88  });
     89  return fetch(r).then(
     90    function (res) {
     91      ok(
     92        isOpaqueResponse(res),
     93        "no-cors Request fetch should result in opaque response"
     94      );
     95    },
     96    function (e) {
     97      ok(false, "no-cors Request fetch should not error");
     98    }
     99  );
    100 }
    101 
    102 function testSameOriginCredentials() {
    103  var cookieStr = "type=chocolatechip";
    104  var tests = [
    105    {
    106      // Initialize by setting a cookie.
    107      pass: 1,
    108      setCookie: cookieStr + "; Partitioned; Secure; SameSite=None",
    109      withCred: "same-origin",
    110    },
    111    {
    112      // Default mode is "same-origin".
    113      pass: 1,
    114      cookie: cookieStr,
    115    },
    116    {
    117      pass: 1,
    118      noCookie: 1,
    119      withCred: "omit",
    120    },
    121    {
    122      pass: 1,
    123      cookie: cookieStr,
    124      withCred: "same-origin",
    125    },
    126    {
    127      pass: 1,
    128      cookie: cookieStr,
    129      withCred: "include",
    130    },
    131  ];
    132 
    133  var finalPromiseResolve, finalPromiseReject;
    134  var finalPromise = new Promise(function (res, rej) {
    135    finalPromiseResolve = res;
    136    finalPromiseReject = rej;
    137  });
    138 
    139  function makeRequest(test) {
    140    req = {
    141      // Add a default query param just to make formatting the actual params
    142      // easier.
    143      url: corsServerPath + "a=b",
    144      method: test.method,
    145      headers: test.headers,
    146      withCred: test.withCred,
    147    };
    148 
    149    if (test.setCookie) {
    150      req.url += "&setCookie=" + escape(test.setCookie);
    151    }
    152    if (test.cookie) {
    153      req.url += "&cookie=" + escape(test.cookie);
    154    }
    155    if (test.noCookie) {
    156      req.url += "&noCookie";
    157    }
    158 
    159    return new Request(req.url, {
    160      method: req.method,
    161      headers: req.headers,
    162      credentials: req.withCred,
    163    });
    164  }
    165 
    166  function testResponse(res, test) {
    167    ok(test.pass, "Expected test to pass " + JSON.stringify(test));
    168    is(res.status, 200, "wrong status in test for " + JSON.stringify(test));
    169    is(res.statusText, "OK", "wrong status text for " + JSON.stringify(test));
    170    return res.text().then(function (v) {
    171      is(
    172        v,
    173        "<res>hello pass</res>\n",
    174        "wrong text in test for " + JSON.stringify(test)
    175      );
    176    });
    177  }
    178 
    179  function runATest(tests, i) {
    180    var test = tests[i];
    181    var request = makeRequest(test);
    182    console.log(request.url);
    183    fetch(request).then(
    184      function (res) {
    185        testResponse(res, test).then(function () {
    186          if (i < tests.length - 1) {
    187            runATest(tests, i + 1);
    188          } else {
    189            finalPromiseResolve();
    190          }
    191        });
    192      },
    193      function (e) {
    194        ok(!test.pass, "Expected test to fail " + JSON.stringify(test));
    195        ok(e instanceof TypeError, "Test should fail " + JSON.stringify(test));
    196        if (i < tests.length - 1) {
    197          runATest(tests, i + 1);
    198        } else {
    199          finalPromiseResolve();
    200        }
    201      }
    202    );
    203  }
    204 
    205  runATest(tests, 0);
    206  return finalPromise;
    207 }
    208 
    209 function testModeCors() {
    210  var tests = [
    211    // Plain request
    212    { pass: 1, method: "GET", noAllowPreflight: 1 },
    213 
    214    // undefined username
    215    { pass: 1, method: "GET", noAllowPreflight: 1, username: undefined },
    216 
    217    // undefined username and password
    218    {
    219      pass: 1,
    220      method: "GET",
    221      noAllowPreflight: 1,
    222      username: undefined,
    223      password: undefined,
    224    },
    225 
    226    // nonempty username
    227    { pass: 0, method: "GET", noAllowPreflight: 1, username: "user" },
    228 
    229    // nonempty password
    230    { pass: 0, method: "GET", noAllowPreflight: 1, password: "password" },
    231 
    232    // Default allowed headers
    233    {
    234      pass: 1,
    235      method: "GET",
    236      headers: {
    237        "Content-Type": "text/plain",
    238        Accept: "foo/bar",
    239        "Accept-Language": "sv-SE",
    240      },
    241      noAllowPreflight: 1,
    242    },
    243 
    244    {
    245      pass: 0,
    246      method: "GET",
    247      headers: {
    248        "Content-Type": "foo/bar",
    249        Accept: "foo/bar",
    250        "Accept-Language": "sv-SE",
    251      },
    252      noAllowPreflight: 1,
    253    },
    254 
    255    {
    256      pass: 0,
    257      method: "GET",
    258      headers: { "Content-Type": "foo/bar, text/plain" },
    259      noAllowPreflight: 1,
    260    },
    261 
    262    {
    263      pass: 0,
    264      method: "GET",
    265      headers: { "Content-Type": "foo/bar, text/plain, garbage" },
    266      noAllowPreflight: 1,
    267    },
    268 
    269    // Custom headers
    270    {
    271      pass: 1,
    272      method: "GET",
    273      headers: { "x-my-header": "myValue" },
    274      allowHeaders: "x-my-header",
    275    },
    276    {
    277      pass: 1,
    278      method: "GET",
    279      headers: { "x-my-header": "myValue" },
    280      allowHeaders: "X-My-Header",
    281    },
    282    {
    283      pass: 1,
    284      method: "GET",
    285      headers: {
    286        "x-my-header": "myValue",
    287        "long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header":
    288          "secondValue",
    289      },
    290      allowHeaders:
    291        "x-my-header, long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header",
    292    },
    293    {
    294      pass: 1,
    295      method: "GET",
    296      headers: { "x-my%-header": "myValue" },
    297      allowHeaders: "x-my%-header",
    298    },
    299    { pass: 0, method: "GET", headers: { "x-my-header": "myValue" } },
    300    { pass: 0, method: "GET", headers: { "x-my-header": "" } },
    301    {
    302      pass: 0,
    303      method: "GET",
    304      headers: { "x-my-header": "myValue" },
    305      allowHeaders: "",
    306    },
    307    {
    308      pass: 0,
    309      method: "GET",
    310      headers: { "x-my-header": "myValue" },
    311      allowHeaders: "y-my-header",
    312    },
    313    {
    314      pass: 0,
    315      method: "GET",
    316      headers: { "x-my-header": "myValue" },
    317      allowHeaders: "x-my-header y-my-header",
    318    },
    319    {
    320      pass: 0,
    321      method: "GET",
    322      headers: { "x-my-header": "myValue" },
    323      allowHeaders: "x-my-header, y-my-header z",
    324    },
    325    {
    326      pass: 0,
    327      method: "GET",
    328      headers: { "x-my-header": "myValue" },
    329      allowHeaders: "x-my-header, y-my-he(ader",
    330    },
    331    {
    332      pass: 0,
    333      method: "GET",
    334      headers: { myheader: "" },
    335      allowMethods: "myheader",
    336    },
    337    {
    338      pass: 1,
    339      method: "GET",
    340      headers: { "User-Agent": "myValue" },
    341      allowHeaders: "User-Agent",
    342    },
    343    { pass: 0, method: "GET", headers: { "User-Agent": "myValue" } },
    344 
    345    // Multiple custom headers
    346    {
    347      pass: 1,
    348      method: "GET",
    349      headers: {
    350        "x-my-header": "myValue",
    351        "second-header": "secondValue",
    352        "third-header": "thirdValue",
    353      },
    354      allowHeaders: "x-my-header, second-header, third-header",
    355    },
    356    {
    357      pass: 1,
    358      method: "GET",
    359      headers: {
    360        "x-my-header": "myValue",
    361        "second-header": "secondValue",
    362        "third-header": "thirdValue",
    363      },
    364      allowHeaders: "x-my-header,second-header,third-header",
    365    },
    366    {
    367      pass: 1,
    368      method: "GET",
    369      headers: {
    370        "x-my-header": "myValue",
    371        "second-header": "secondValue",
    372        "third-header": "thirdValue",
    373      },
    374      allowHeaders: "x-my-header ,second-header ,third-header",
    375    },
    376    {
    377      pass: 1,
    378      method: "GET",
    379      headers: {
    380        "x-my-header": "myValue",
    381        "second-header": "secondValue",
    382        "third-header": "thirdValue",
    383      },
    384      allowHeaders: "x-my-header , second-header , third-header",
    385    },
    386    {
    387      pass: 1,
    388      method: "GET",
    389      headers: { "x-my-header": "myValue", "second-header": "secondValue" },
    390      allowHeaders: ",  x-my-header, , ,, second-header, ,   ",
    391    },
    392    {
    393      pass: 1,
    394      method: "GET",
    395      headers: { "x-my-header": "myValue", "second-header": "secondValue" },
    396      allowHeaders: "x-my-header, second-header, unused-header",
    397    },
    398    {
    399      pass: 0,
    400      method: "GET",
    401      headers: { "x-my-header": "myValue", "y-my-header": "secondValue" },
    402      allowHeaders: "x-my-header",
    403    },
    404    {
    405      pass: 0,
    406      method: "GET",
    407      headers: { "x-my-header": "", "y-my-header": "" },
    408      allowHeaders: "x-my-header",
    409    },
    410 
    411    // HEAD requests
    412    { pass: 1, method: "HEAD", noAllowPreflight: 1 },
    413 
    414    // HEAD with safe headers
    415    {
    416      pass: 1,
    417      method: "HEAD",
    418      headers: {
    419        "Content-Type": "text/plain",
    420        Accept: "foo/bar",
    421        "Accept-Language": "sv-SE",
    422      },
    423      noAllowPreflight: 1,
    424    },
    425    {
    426      pass: 0,
    427      method: "HEAD",
    428      headers: {
    429        "Content-Type": "foo/bar",
    430        Accept: "foo/bar",
    431        "Accept-Language": "sv-SE",
    432      },
    433      noAllowPreflight: 1,
    434    },
    435    {
    436      pass: 0,
    437      method: "HEAD",
    438      headers: { "Content-Type": "foo/bar, text/plain" },
    439      noAllowPreflight: 1,
    440    },
    441    {
    442      pass: 0,
    443      method: "HEAD",
    444      headers: { "Content-Type": "foo/bar, text/plain, garbage" },
    445      noAllowPreflight: 1,
    446    },
    447 
    448    // HEAD with custom headers
    449    {
    450      pass: 1,
    451      method: "HEAD",
    452      headers: { "x-my-header": "myValue" },
    453      allowHeaders: "x-my-header",
    454    },
    455    { pass: 0, method: "HEAD", headers: { "x-my-header": "myValue" } },
    456    {
    457      pass: 0,
    458      method: "HEAD",
    459      headers: { "x-my-header": "myValue" },
    460      allowHeaders: "",
    461    },
    462    {
    463      pass: 0,
    464      method: "HEAD",
    465      headers: { "x-my-header": "myValue" },
    466      allowHeaders: "y-my-header",
    467    },
    468    {
    469      pass: 0,
    470      method: "HEAD",
    471      headers: { "x-my-header": "myValue" },
    472      allowHeaders: "x-my-header y-my-header",
    473    },
    474 
    475    // POST tests
    476    { pass: 1, method: "POST", body: "hi there", noAllowPreflight: 1 },
    477    { pass: 1, method: "POST" },
    478    { pass: 1, method: "POST", noAllowPreflight: 1 },
    479 
    480    // POST with standard headers
    481    {
    482      pass: 1,
    483      method: "POST",
    484      body: "hi there",
    485      headers: { "Content-Type": "text/plain" },
    486      noAllowPreflight: 1,
    487    },
    488    {
    489      pass: 1,
    490      method: "POST",
    491      body: "hi there",
    492      headers: { "Content-Type": "multipart/form-data" },
    493      noAllowPreflight: 1,
    494    },
    495    {
    496      pass: 1,
    497      method: "POST",
    498      body: "hi there",
    499      headers: { "Content-Type": "application/x-www-form-urlencoded" },
    500      noAllowPreflight: 1,
    501    },
    502    {
    503      pass: 0,
    504      method: "POST",
    505      body: "hi there",
    506      headers: { "Content-Type": "foo/bar" },
    507    },
    508    { pass: 0, method: "POST", headers: { "Content-Type": "foo/bar" } },
    509    {
    510      pass: 1,
    511      method: "POST",
    512      body: "hi there",
    513      headers: {
    514        "Content-Type": "text/plain",
    515        Accept: "foo/bar",
    516        "Accept-Language": "sv-SE",
    517      },
    518      noAllowPreflight: 1,
    519    },
    520    {
    521      pass: 0,
    522      method: "POST",
    523      body: "hi there",
    524      headers: { "Content-Type": "foo/bar, text/plain" },
    525      noAllowPreflight: 1,
    526    },
    527    {
    528      pass: 0,
    529      method: "POST",
    530      body: "hi there",
    531      headers: { "Content-Type": "foo/bar, text/plain, garbage" },
    532      noAllowPreflight: 1,
    533    },
    534 
    535    // POST with custom headers
    536    {
    537      pass: 1,
    538      method: "POST",
    539      body: "hi there",
    540      headers: {
    541        Accept: "foo/bar",
    542        "Accept-Language": "sv-SE",
    543        "x-my-header": "myValue",
    544      },
    545      allowHeaders: "x-my-header",
    546    },
    547    {
    548      pass: 1,
    549      method: "POST",
    550      headers: { "Content-Type": "text/plain", "x-my-header": "myValue" },
    551      allowHeaders: "x-my-header",
    552    },
    553    {
    554      pass: 1,
    555      method: "POST",
    556      body: "hi there",
    557      headers: { "Content-Type": "text/plain", "x-my-header": "myValue" },
    558      allowHeaders: "x-my-header",
    559    },
    560    {
    561      pass: 1,
    562      method: "POST",
    563      body: "hi there",
    564      headers: { "Content-Type": "foo/bar", "x-my-header": "myValue" },
    565      allowHeaders: "x-my-header, content-type",
    566    },
    567    {
    568      pass: 0,
    569      method: "POST",
    570      body: "hi there",
    571      headers: { "Content-Type": "foo/bar" },
    572      noAllowPreflight: 1,
    573    },
    574    {
    575      pass: 0,
    576      method: "POST",
    577      body: "hi there",
    578      headers: { "Content-Type": "foo/bar", "x-my-header": "myValue" },
    579      allowHeaders: "x-my-header",
    580    },
    581    {
    582      pass: 1,
    583      method: "POST",
    584      headers: { "x-my-header": "myValue" },
    585      allowHeaders: "x-my-header",
    586    },
    587    {
    588      pass: 1,
    589      method: "POST",
    590      body: "hi there",
    591      headers: { "x-my-header": "myValue" },
    592      allowHeaders: "x-my-header, $_%",
    593    },
    594 
    595    // Other methods
    596    { pass: 1, method: "DELETE", allowMethods: "DELETE" },
    597    { pass: 0, method: "DELETE", allowHeaders: "DELETE" },
    598    { pass: 0, method: "DELETE" },
    599    { pass: 0, method: "DELETE", allowMethods: "" },
    600    { pass: 1, method: "DELETE", allowMethods: "POST, PUT, DELETE" },
    601    { pass: 1, method: "DELETE", allowMethods: "POST, DELETE, PUT" },
    602    { pass: 1, method: "DELETE", allowMethods: "DELETE, POST, PUT" },
    603    { pass: 1, method: "DELETE", allowMethods: "POST ,PUT ,DELETE" },
    604    { pass: 1, method: "DELETE", allowMethods: "POST,PUT,DELETE" },
    605    { pass: 1, method: "DELETE", allowMethods: "POST , PUT , DELETE" },
    606    {
    607      pass: 1,
    608      method: "DELETE",
    609      allowMethods: "  ,,  PUT ,,  ,    , DELETE  ,  ,",
    610    },
    611    { pass: 0, method: "DELETE", allowMethods: "PUT" },
    612    { pass: 0, method: "DELETE", allowMethods: "DELETEZ" },
    613    { pass: 0, method: "DELETE", allowMethods: "DELETE PUT" },
    614    { pass: 0, method: "DELETE", allowMethods: "DELETE, PUT Z" },
    615    { pass: 0, method: "DELETE", allowMethods: "DELETE, PU(T" },
    616    { pass: 0, method: "DELETE", allowMethods: "PUT DELETE" },
    617    { pass: 0, method: "DELETE", allowMethods: "PUT Z, DELETE" },
    618    { pass: 0, method: "DELETE", allowMethods: "PU(T, DELETE" },
    619    { pass: 0, method: "PUT", allowMethods: "put" },
    620 
    621    // Status messages
    622    {
    623      pass: 1,
    624      method: "GET",
    625      noAllowPreflight: 1,
    626      status: 404,
    627      statusMessage: "nothin' here",
    628    },
    629    {
    630      pass: 1,
    631      method: "GET",
    632      noAllowPreflight: 1,
    633      status: 401,
    634      statusMessage: "no can do",
    635    },
    636    {
    637      pass: 1,
    638      method: "POST",
    639      body: "hi there",
    640      headers: { "Content-Type": "foo/bar" },
    641      allowHeaders: "content-type",
    642      status: 500,
    643      statusMessage: "server boo",
    644    },
    645    {
    646      pass: 1,
    647      method: "GET",
    648      noAllowPreflight: 1,
    649      status: 200,
    650      statusMessage: "Yes!!",
    651    },
    652    {
    653      pass: 0,
    654      method: "GET",
    655      headers: { "x-my-header": "header value" },
    656      allowHeaders: "x-my-header",
    657      preflightStatus: 400,
    658    },
    659    {
    660      pass: 1,
    661      method: "GET",
    662      headers: { "x-my-header": "header value" },
    663      allowHeaders: "x-my-header",
    664      preflightStatus: 200,
    665    },
    666    {
    667      pass: 1,
    668      method: "GET",
    669      headers: { "x-my-header": "header value" },
    670      allowHeaders: "x-my-header",
    671      preflightStatus: 204,
    672    },
    673 
    674    // exposed headers
    675    {
    676      pass: 1,
    677      method: "GET",
    678      responseHeaders: { "x-my-header": "x header" },
    679      exposeHeaders: "x-my-header",
    680      expectedResponseHeaders: ["x-my-header"],
    681    },
    682    {
    683      pass: 0,
    684      method: "GET",
    685      origin: "http://invalid",
    686      responseHeaders: { "x-my-header": "x header" },
    687      exposeHeaders: "x-my-header",
    688      expectedResponseHeaders: [],
    689    },
    690    {
    691      pass: 1,
    692      method: "GET",
    693      responseHeaders: { "x-my-header": "x header" },
    694      expectedResponseHeaders: [],
    695    },
    696    {
    697      pass: 1,
    698      method: "GET",
    699      responseHeaders: { "x-my-header": "x header" },
    700      exposeHeaders: "x-my-header y",
    701      expectedResponseHeaders: [],
    702    },
    703    {
    704      pass: 1,
    705      method: "GET",
    706      responseHeaders: { "x-my-header": "x header" },
    707      exposeHeaders: "y x-my-header",
    708      expectedResponseHeaders: [],
    709    },
    710    {
    711      pass: 1,
    712      method: "GET",
    713      responseHeaders: { "x-my-header": "x header" },
    714      exposeHeaders: "x-my-header, y-my-header z",
    715      expectedResponseHeaders: [],
    716    },
    717    {
    718      pass: 1,
    719      method: "GET",
    720      responseHeaders: { "x-my-header": "x header" },
    721      exposeHeaders: "x-my-header, y-my-hea(er",
    722      expectedResponseHeaders: [],
    723    },
    724    {
    725      pass: 1,
    726      method: "GET",
    727      responseHeaders: { "x-my-header": "x header", "y-my-header": "y header" },
    728      exposeHeaders: "  ,  ,,y-my-header,z-my-header,  ",
    729      expectedResponseHeaders: ["y-my-header"],
    730    },
    731    {
    732      pass: 1,
    733      method: "GET",
    734      responseHeaders: {
    735        "Cache-Control": "cacheControl header",
    736        "Content-Language": "contentLanguage header",
    737        Expires: "expires header",
    738        "Last-Modified": "lastModified header",
    739        Pragma: "pragma header",
    740        Unexpected: "unexpected header",
    741      },
    742      expectedResponseHeaders: [
    743        "Cache-Control",
    744        "Content-Language",
    745        "Content-Type",
    746        "Expires",
    747        "Last-Modified",
    748        "Pragma",
    749      ],
    750    },
    751    // Check that sending a body in the OPTIONS response works
    752    {
    753      pass: 1,
    754      method: "DELETE",
    755      allowMethods: "DELETE",
    756      preflightBody: "I'm a preflight response body",
    757    },
    758  ];
    759 
    760  var origin = self.location.origin;
    761  var baseURL =
    762    origin == "https://example.com"
    763      ? "https://example.org"
    764      : "https://example.com";
    765  baseURL += corsServerPath;
    766 
    767  var fetches = [];
    768  for (test of tests) {
    769    var req = {
    770      url: baseURL + "allowOrigin=" + escape(test.origin || origin),
    771      method: test.method,
    772      headers: test.headers,
    773      uploadProgress: test.uploadProgress,
    774      body: test.body,
    775      responseHeaders: test.responseHeaders,
    776    };
    777 
    778    if (test.pass) {
    779      req.url += "&origin=" + escape(origin) + "&requestMethod=" + test.method;
    780    }
    781 
    782    if ("username" in test) {
    783      var u = new URL(req.url);
    784      u.username = test.username || "";
    785      req.url = u.href;
    786    }
    787 
    788    if ("password" in test) {
    789      var u = new URL(req.url);
    790      u.password = test.password || "";
    791      req.url = u.href;
    792    }
    793 
    794    if (test.noAllowPreflight) {
    795      req.url += "&noAllowPreflight";
    796    }
    797 
    798    if (test.pass && "headers" in test) {
    799      function isUnsafeHeader(name) {
    800        lName = name.toLowerCase();
    801        return (
    802          lName != "accept" &&
    803          lName != "accept-language" &&
    804          (lName != "content-type" ||
    805            ![
    806              "text/plain",
    807              "multipart/form-data",
    808              "application/x-www-form-urlencoded",
    809            ].includes(test.headers[name].toLowerCase()))
    810        );
    811      }
    812      req.url += "&headers=" + escape(JSON.stringify(test.headers));
    813      reqHeaders = escape(
    814        Object.keys(test.headers)
    815          .filter(isUnsafeHeader)
    816          .map(s => s.toLowerCase())
    817          .sort()
    818          .join(",")
    819      );
    820      req.url += reqHeaders ? "&requestHeaders=" + reqHeaders : "";
    821    }
    822    if ("allowHeaders" in test) {
    823      req.url += "&allowHeaders=" + escape(test.allowHeaders);
    824    }
    825    if ("allowMethods" in test) {
    826      req.url += "&allowMethods=" + escape(test.allowMethods);
    827    }
    828    if (test.body) {
    829      req.url += "&body=" + escape(test.body);
    830    }
    831    if (test.status) {
    832      req.url += "&status=" + test.status;
    833      req.url += "&statusMessage=" + escape(test.statusMessage);
    834    }
    835    if (test.preflightStatus) {
    836      req.url += "&preflightStatus=" + test.preflightStatus;
    837    }
    838    if (test.responseHeaders) {
    839      req.url +=
    840        "&responseHeaders=" + escape(JSON.stringify(test.responseHeaders));
    841    }
    842    if (test.exposeHeaders) {
    843      req.url += "&exposeHeaders=" + escape(test.exposeHeaders);
    844    }
    845    if (test.preflightBody) {
    846      req.url += "&preflightBody=" + escape(test.preflightBody);
    847    }
    848 
    849    fetches.push(
    850      (function (test) {
    851        return new Promise(function (resolve) {
    852          resolve(
    853            new Request(req.url, {
    854              method: req.method,
    855              mode: "cors",
    856              headers: req.headers,
    857              body: req.body,
    858            })
    859          );
    860        })
    861          .then(function (request) {
    862            return fetch(request);
    863          })
    864          .then(function (res) {
    865            ok(test.pass, "Expected test to pass for " + JSON.stringify(test));
    866            if (test.status) {
    867              is(
    868                res.status,
    869                test.status,
    870                "wrong status in test for " + JSON.stringify(test)
    871              );
    872              is(
    873                res.statusText,
    874                test.statusMessage,
    875                "wrong status text for " + JSON.stringify(test)
    876              );
    877            } else {
    878              is(
    879                res.status,
    880                200,
    881                "wrong status in test for " + JSON.stringify(test)
    882              );
    883              is(
    884                res.statusText,
    885                "OK",
    886                "wrong status text for " + JSON.stringify(test)
    887              );
    888            }
    889            if (test.responseHeaders) {
    890              for (header in test.responseHeaders) {
    891                if (!test.expectedResponseHeaders.includes(header)) {
    892                  is(
    893                    res.headers.has(header),
    894                    false,
    895                    "|Headers.has()|wrong response header (" +
    896                      header +
    897                      ") in test for " +
    898                      JSON.stringify(test)
    899                  );
    900                } else {
    901                  is(
    902                    res.headers.get(header),
    903                    test.responseHeaders[header],
    904                    "|Headers.get()|wrong response header (" +
    905                      header +
    906                      ") in test for " +
    907                      JSON.stringify(test)
    908                  );
    909                }
    910              }
    911            }
    912 
    913            return res.text();
    914          })
    915          .then(function (v) {
    916            if (test.method !== "HEAD") {
    917              is(
    918                v,
    919                "<res>hello pass</res>\n",
    920                "wrong responseText in test for " + JSON.stringify(test)
    921              );
    922            } else {
    923              is(
    924                v,
    925                "",
    926                "wrong responseText in HEAD test for " + JSON.stringify(test)
    927              );
    928            }
    929          })
    930          .catch(function (e) {
    931            ok(!test.pass, "Expected test failure for " + JSON.stringify(test));
    932            ok(
    933              e instanceof TypeError,
    934              "Exception should be TypeError for " + JSON.stringify(test)
    935            );
    936          });
    937      })(test)
    938    );
    939  }
    940 
    941  return Promise.all(fetches);
    942 }
    943 
    944 function testCrossOriginCredentials() {
    945  var origin = self.location.origin;
    946 
    947  var tests = [
    948    { pass: 1, method: "GET", withCred: "include", allowCred: 1 },
    949    { pass: 0, method: "GET", withCred: "include", allowCred: 0 },
    950    { pass: 0, method: "GET", withCred: "include", allowCred: 1, origin: "*" },
    951    { pass: 1, method: "GET", withCred: "omit", allowCred: 1, origin: "*" },
    952    {
    953      pass: 1,
    954      method: "GET",
    955      setCookie: "a=1; Partitioned; Secure; SameSite=None",
    956      withCred: "include",
    957      allowCred: 1,
    958    },
    959    {
    960      pass: 1,
    961      method: "GET",
    962      cookie: "a=1",
    963      withCred: "include",
    964      allowCred: 1,
    965    },
    966    { pass: 1, method: "GET", noCookie: 1, withCred: "omit", allowCred: 1 },
    967    { pass: 0, method: "GET", noCookie: 1, withCred: "include", allowCred: 1 },
    968    {
    969      pass: 1,
    970      method: "GET",
    971      setCookie: "a=2; Partitioned; Secure; SameSite=None",
    972      withCred: "omit",
    973      allowCred: 1,
    974    },
    975    {
    976      pass: 1,
    977      method: "GET",
    978      cookie: "a=1",
    979      withCred: "include",
    980      allowCred: 1,
    981    },
    982    {
    983      pass: 1,
    984      method: "GET",
    985      setCookie: "a=2; Partitioned; Secure; SameSite=None",
    986      withCred: "include",
    987      allowCred: 1,
    988    },
    989    {
    990      pass: 1,
    991      method: "GET",
    992      cookie: "a=2",
    993      withCred: "include",
    994      allowCred: 1,
    995    },
    996    {
    997      // When credentials mode is same-origin, but mode is cors, no
    998      // cookie should be sent cross origin.
    999      pass: 0,
   1000      method: "GET",
   1001      cookie: "a=2",
   1002      withCred: "same-origin",
   1003      allowCred: 1,
   1004    },
   1005    {
   1006      // When credentials mode is same-origin, but mode is cors, no
   1007      // cookie should be sent cross origin. This test checks the same
   1008      // thing as above, but uses the noCookie check on the server
   1009      // instead, and expects a valid response.
   1010      pass: 1,
   1011      method: "GET",
   1012      noCookie: 1,
   1013      withCred: "same-origin",
   1014    },
   1015    {
   1016      // Initialize by setting a cookies for same- and cross- origins.
   1017      pass: 1,
   1018      hops: [
   1019        {
   1020          server: origin,
   1021          setCookie: escape("a=1; Partitioned; Secure; SameSite=None"),
   1022        },
   1023        {
   1024          server: "https://example.net",
   1025          allowOrigin: origin,
   1026          allowCred: 1,
   1027          setCookie: escape("a=2; Partitioned; Secure; SameSite=None"),
   1028        },
   1029      ],
   1030      withCred: "include",
   1031    },
   1032    {
   1033      pass: 1,
   1034      method: "GET",
   1035      hops: [
   1036        { server: origin, cookie: escape("a=1") },
   1037        { server: origin, cookie: escape("a=1") },
   1038        { server: "https://example.net", allowOrigin: origin, noCookie: 1 },
   1039      ],
   1040      withCred: "same-origin",
   1041    },
   1042    {
   1043      pass: 1,
   1044      method: "GET",
   1045      hops: [
   1046        { server: origin, cookie: escape("a=1") },
   1047        { server: origin, cookie: escape("a=1") },
   1048        {
   1049          server: "https://example.net",
   1050          allowOrigin: origin,
   1051          allowCred: 1,
   1052          cookie: escape("a=2"),
   1053        },
   1054      ],
   1055      withCred: "include",
   1056    },
   1057    {
   1058      pass: 1,
   1059      method: "GET",
   1060      hops: [
   1061        { server: origin, cookie: escape("a=1") },
   1062        { server: origin, cookie: escape("a=1") },
   1063        { server: "https://example.net", allowOrigin: "*", noCookie: 1 },
   1064      ],
   1065      withCred: "same-origin",
   1066    },
   1067    {
   1068      pass: 0,
   1069      method: "GET",
   1070      hops: [
   1071        { server: origin, cookie: escape("a=1") },
   1072        { server: origin, cookie: escape("a=1") },
   1073        {
   1074          server: "https://example.net",
   1075          allowOrigin: "*",
   1076          allowCred: 1,
   1077          cookie: escape("a=2"),
   1078        },
   1079      ],
   1080      withCred: "include",
   1081    },
   1082    // fails because allow-credentials CORS header is not set by server
   1083    {
   1084      pass: 0,
   1085      method: "GET",
   1086      hops: [
   1087        { server: origin, cookie: escape("a=1") },
   1088        { server: origin, cookie: escape("a=1") },
   1089        {
   1090          server: "https://example.net",
   1091          allowOrigin: origin,
   1092          cookie: escape("a=2"),
   1093        },
   1094      ],
   1095      withCred: "include",
   1096    },
   1097    {
   1098      pass: 1,
   1099      method: "GET",
   1100      hops: [
   1101        { server: origin, noCookie: 1 },
   1102        { server: origin, noCookie: 1 },
   1103        { server: "https://example.net", allowOrigin: origin, noCookie: 1 },
   1104      ],
   1105      withCred: "omit",
   1106    },
   1107  ];
   1108 
   1109  // Xorigin with https has .org origin, default .com
   1110  var baseURL =
   1111    origin == "https://example.com"
   1112      ? "https://example.org"
   1113      : "https://example.com";
   1114  baseURL += corsServerPath;
   1115 
   1116  var finalPromiseResolve, finalPromiseReject;
   1117  var finalPromise = new Promise(function (res, rej) {
   1118    finalPromiseResolve = res;
   1119    finalPromiseReject = rej;
   1120  });
   1121 
   1122  function makeRequest(test) {
   1123    var url;
   1124    if (test.hops) {
   1125      url =
   1126        test.hops[0].server +
   1127        corsServerPath +
   1128        "hop=1&hops=" +
   1129        escape(JSON.stringify(test.hops));
   1130    } else {
   1131      url = baseURL + "allowOrigin=" + escape(test.origin || origin);
   1132    }
   1133    req = {
   1134      url,
   1135      method: test.method,
   1136      headers: test.headers,
   1137      withCred: test.withCred,
   1138    };
   1139 
   1140    if (test.allowCred) {
   1141      req.url += "&allowCred";
   1142    }
   1143 
   1144    if (test.setCookie) {
   1145      req.url += "&setCookie=" + escape(test.setCookie);
   1146    }
   1147    if (test.cookie) {
   1148      req.url += "&cookie=" + escape(test.cookie);
   1149    }
   1150    if (test.noCookie) {
   1151      req.url += "&noCookie";
   1152    }
   1153 
   1154    if ("allowHeaders" in test) {
   1155      req.url += "&allowHeaders=" + escape(test.allowHeaders);
   1156    }
   1157    if ("allowMethods" in test) {
   1158      req.url += "&allowMethods=" + escape(test.allowMethods);
   1159    }
   1160 
   1161    return new Request(req.url, {
   1162      method: req.method,
   1163      headers: req.headers,
   1164      credentials: req.withCred,
   1165    });
   1166  }
   1167 
   1168  function testResponse(res, test) {
   1169    ok(test.pass, "Expected test to pass for " + JSON.stringify(test));
   1170    is(res.status, 200, "wrong status in test for " + JSON.stringify(test));
   1171    is(res.statusText, "OK", "wrong status text for " + JSON.stringify(test));
   1172    return res.text().then(function (v) {
   1173      is(
   1174        v,
   1175        "<res>hello pass</res>\n",
   1176        "wrong text in test for " + JSON.stringify(test)
   1177      );
   1178    });
   1179  }
   1180 
   1181  function runATest(tests, i) {
   1182    var test = tests[i];
   1183    var request = makeRequest(test);
   1184    fetch(request).then(
   1185      function (res) {
   1186        testResponse(res, test).then(function () {
   1187          if (i < tests.length - 1) {
   1188            runATest(tests, i + 1);
   1189          } else {
   1190            finalPromiseResolve();
   1191          }
   1192        });
   1193      },
   1194      function (e) {
   1195        ok(!test.pass, "Expected test failure for " + JSON.stringify(test));
   1196        ok(
   1197          e instanceof TypeError,
   1198          "Exception should be TypeError for " + JSON.stringify(test)
   1199        );
   1200        if (i < tests.length - 1) {
   1201          runATest(tests, i + 1);
   1202        } else {
   1203          finalPromiseResolve();
   1204        }
   1205      }
   1206    );
   1207  }
   1208 
   1209  runATest(tests, 0);
   1210  return finalPromise;
   1211 }
   1212 
   1213 function testModeNoCorsCredentials() {
   1214  var cookieStr = "type=chocolatechip";
   1215  var tests = [
   1216    {
   1217      // Initialize by setting a cookie.
   1218      pass: 1,
   1219      setCookie: cookieStr + "; Partitioned; Secure; SameSite=None",
   1220      withCred: "include",
   1221    },
   1222    {
   1223      pass: 1,
   1224      noCookie: 1,
   1225      withCred: "omit",
   1226    },
   1227    {
   1228      pass: 1,
   1229      noCookie: 1,
   1230      withCred: "same-origin",
   1231    },
   1232    {
   1233      pass: 1,
   1234      cookie: cookieStr,
   1235      withCred: "include",
   1236    },
   1237    {
   1238      pass: 1,
   1239      cookie: cookieStr,
   1240      withCred: "omit",
   1241      status: 500,
   1242    },
   1243    {
   1244      pass: 1,
   1245      cookie: cookieStr,
   1246      withCred: "same-origin",
   1247      status: 500,
   1248    },
   1249    {
   1250      pass: 1,
   1251      noCookie: 1,
   1252      withCred: "include",
   1253      status: 500,
   1254    },
   1255  ];
   1256 
   1257  var finalPromiseResolve, finalPromiseReject;
   1258  var finalPromise = new Promise(function (res, rej) {
   1259    finalPromiseResolve = res;
   1260    finalPromiseReject = rej;
   1261  });
   1262 
   1263  function makeRequest(test) {
   1264    req = {
   1265      url: "https://example.net" + corsServerPath + "a+b",
   1266      withCred: test.withCred,
   1267    };
   1268 
   1269    if (test.setCookie) {
   1270      req.url += "&setCookie=" + escape(test.setCookie);
   1271    }
   1272    if (test.cookie) {
   1273      req.url += "&cookie=" + escape(test.cookie);
   1274    }
   1275    if (test.noCookie) {
   1276      req.url += "&noCookie";
   1277    }
   1278 
   1279    return new Request(req.url, {
   1280      method: "GET",
   1281      mode: "no-cors",
   1282      credentials: req.withCred,
   1283    });
   1284  }
   1285 
   1286  function testResponse(res, test) {
   1287    is(res.type, "opaque", "wrong response type for " + JSON.stringify(test));
   1288 
   1289    // Get unfiltered response
   1290    var chromeResponse = SpecialPowers.wrap(res);
   1291    var unfiltered = chromeResponse.cloneUnfiltered();
   1292 
   1293    var status = test.status ? test.status : 200;
   1294    is(
   1295      unfiltered.status,
   1296      status,
   1297      "wrong status in test for " + JSON.stringify(test)
   1298    );
   1299 
   1300    return unfiltered.text().then(function (v) {
   1301      if (test.status === 200) {
   1302        const expected =
   1303          SpecialPowers.getIntPref(
   1304            "browser.opaqueResponseBlocking.filterFetchResponse"
   1305          ) > 0
   1306            ? ""
   1307            : "<res>hello pass</res>\n";
   1308        is(v, expected, "wrong text in test for " + JSON.stringify(test));
   1309      }
   1310    });
   1311  }
   1312 
   1313  function runATest(tests, i) {
   1314    if (typeof SpecialPowers !== "object") {
   1315      finalPromiseResolve();
   1316      return;
   1317    }
   1318 
   1319    var test = tests[i];
   1320    var request = makeRequest(test);
   1321    fetch(request).then(
   1322      function (res) {
   1323        ok(test.pass, "Expected test to pass " + JSON.stringify(test));
   1324        testResponse(res, test).then(function () {
   1325          if (i < tests.length - 1) {
   1326            runATest(tests, i + 1);
   1327          } else {
   1328            finalPromiseResolve();
   1329          }
   1330        });
   1331      },
   1332      function (e) {
   1333        ok(!test.pass, "Expected test to fail " + JSON.stringify(test));
   1334        ok(e instanceof TypeError, "Test should fail " + JSON.stringify(test));
   1335        if (i < tests.length - 1) {
   1336          runATest(tests, i + 1);
   1337        } else {
   1338          finalPromiseResolve();
   1339        }
   1340      }
   1341    );
   1342  }
   1343 
   1344  runATest(tests, 0);
   1345  return finalPromise;
   1346 }
   1347 
   1348 function testCORSRedirects() {
   1349  var origin = self.location.origin;
   1350 
   1351  var host = self.location.hostname;
   1352  var protocol = self.location.protocol;
   1353  var originSubSub1 = protocol + "//sub1.test1." + host;
   1354  var originSubSub2 = protocol + "//sub2.test2." + host;
   1355  var originSub = protocol + "//test3." + host;
   1356 
   1357  var foreignHost = host === "example.com" ? "example.org" : "example.com";
   1358  var foreignSub1 = protocol + "//test1." + foreignHost;
   1359  var foreignSub2 = protocol + "//test2." + foreignHost;
   1360 
   1361  var tests = [
   1362    {
   1363      pass: 1,
   1364      method: "GET",
   1365      hops: [{ server: "https://example.net", allowOrigin: origin }],
   1366    },
   1367    {
   1368      pass: 0,
   1369      method: "GET",
   1370      hops: [
   1371        { server: "https://example.net", allowOrigin: origin },
   1372        { server: origin, allowOrigin: origin },
   1373      ],
   1374    },
   1375    {
   1376      pass: 1,
   1377      method: "GET",
   1378      hops: [
   1379        { server: "https://example.net", allowOrigin: origin },
   1380        { server: origin, allowOrigin: "*" },
   1381      ],
   1382    },
   1383    {
   1384      pass: 0,
   1385      method: "GET",
   1386      hops: [
   1387        { server: "https://example.net", allowOrigin: origin },
   1388        { server: origin },
   1389      ],
   1390    },
   1391    {
   1392      pass: 1,
   1393      method: "GET",
   1394      hops: [
   1395        { server: origin },
   1396        { server: origin },
   1397        { server: "https://example.net", allowOrigin: origin },
   1398      ],
   1399    },
   1400    {
   1401      pass: 0,
   1402      method: "GET",
   1403      hops: [
   1404        { server: origin },
   1405        { server: origin },
   1406        { server: "https://example.net", allowOrigin: origin },
   1407        { server: origin },
   1408      ],
   1409    },
   1410    {
   1411      pass: 0,
   1412      method: "GET",
   1413      hops: [
   1414        { server: "https://example.net", allowOrigin: origin },
   1415        { server: originSub, allowOrigin: origin },
   1416        { server: originSubSub1, allowOrigin: origin },
   1417        { server: originSubSub2, allowOrigin: origin },
   1418      ],
   1419    },
   1420    {
   1421      pass: 0,
   1422      method: "GET",
   1423      hops: [
   1424        { server: "https://example.net", allowOrigin: origin },
   1425        { server: originSub, allowOrigin: origin },
   1426        { server: originSubSub1, allowOrigin: "*" },
   1427        { server: originSubSub2, allowOrigin: "*" },
   1428      ],
   1429    },
   1430    {
   1431      pass: 1,
   1432      method: "GET",
   1433      hops: [
   1434        { server: "https://example.net", allowOrigin: origin },
   1435        { server: originSub, allowOrigin: "*" },
   1436        { server: originSubSub1, allowOrigin: "*" },
   1437        { server: originSubSub2, allowOrigin: "*" },
   1438      ],
   1439    },
   1440    {
   1441      pass: 0,
   1442      method: "GET",
   1443      hops: [
   1444        { server: "https://example.net", allowOrigin: origin },
   1445        { server: originSub, allowOrigin: origin },
   1446        { server: originSubSub1, allowOrigin: "x" },
   1447        { server: originSubSub2, allowOrigin: origin },
   1448      ],
   1449    },
   1450    {
   1451      pass: 0,
   1452      method: "GET",
   1453      hops: [
   1454        { server: "https://example.net", allowOrigin: origin },
   1455        { server: originSub, allowOrigin: origin },
   1456        { server: originSubSub1, allowOrigin: "*" },
   1457        { server: originSubSub2, allowOrigin: origin },
   1458      ],
   1459    },
   1460    {
   1461      pass: 0,
   1462      method: "GET",
   1463      hops: [
   1464        { server: "https://example.net", allowOrigin: origin },
   1465        { server: originSub, allowOrigin: origin },
   1466        { server: originSubSub1, allowOrigin: "*" },
   1467        { server: originSubSub2 },
   1468      ],
   1469    },
   1470    {
   1471      pass: 1,
   1472      method: "POST",
   1473      body: "hi there",
   1474      headers: { "Content-Type": "text/plain" },
   1475      hops: [
   1476        { server: origin },
   1477        { server: "https://example.net", allowOrigin: origin },
   1478      ],
   1479    },
   1480    {
   1481      pass: 1,
   1482      method: "POST",
   1483      body: "hi there",
   1484      headers: { "Content-Type": "text/plain", "my-header": "myValue" },
   1485      hops: [
   1486        { server: origin },
   1487        {
   1488          server: "https://example.net",
   1489          allowOrigin: origin,
   1490          allowHeaders: "my-header",
   1491        },
   1492      ],
   1493    },
   1494    {
   1495      pass: 0,
   1496      method: "POST",
   1497      body: "hi there",
   1498      headers: { "Content-Type": "text/plain", "my-header": "myValue" },
   1499      hops: [
   1500        { server: origin },
   1501        {
   1502          server: "https://example.net",
   1503          allowOrigin: origin,
   1504          allowHeaders: "my-header",
   1505          noAllowPreflight: 1,
   1506        },
   1507      ],
   1508    },
   1509    {
   1510      pass: 0,
   1511      method: "POST",
   1512      body: "hi there",
   1513      headers: { "Content-Type": "text/plain", "my-header": "myValue" },
   1514      hops: [
   1515        { server: origin },
   1516        {
   1517          server: foreignSub1,
   1518          allowOrigin: origin,
   1519          allowHeaders: "my-header",
   1520        },
   1521        {
   1522          server: foreignSub2,
   1523          allowOrigin: origin,
   1524          allowHeaders: "my-header",
   1525        },
   1526      ],
   1527    },
   1528    {
   1529      pass: 1,
   1530      method: "DELETE",
   1531      hops: [
   1532        { server: origin },
   1533        {
   1534          server: "https://example.net",
   1535          allowOrigin: origin,
   1536          allowMethods: "DELETE",
   1537        },
   1538      ],
   1539    },
   1540    {
   1541      pass: 0,
   1542      method: "DELETE",
   1543      hops: [
   1544        { server: origin },
   1545        {
   1546          server: "https://example.net",
   1547          allowOrigin: origin,
   1548          allowMethods: "DELETE",
   1549          noAllowPreflight: 1,
   1550        },
   1551      ],
   1552    },
   1553    {
   1554      pass: 0,
   1555      method: "DELETE",
   1556      hops: [
   1557        { server: origin },
   1558        {
   1559          server: foreignSub1,
   1560          allowOrigin: origin,
   1561          allowMethods: "DELETE",
   1562        },
   1563        {
   1564          server: foreignSub2,
   1565          allowOrigin: origin,
   1566          allowMethods: "DELETE",
   1567        },
   1568      ],
   1569    },
   1570    {
   1571      pass: 0,
   1572      method: "POST",
   1573      body: "hi there",
   1574      headers: { "Content-Type": "text/plain", "my-header": "myValue" },
   1575      hops: [
   1576        { server: "https://example.net", allowOrigin: origin },
   1577        { server: originSubSub1, allowOrigin: origin },
   1578      ],
   1579    },
   1580    {
   1581      pass: 0,
   1582      method: "DELETE",
   1583      hops: [
   1584        {
   1585          server: "https://example.net",
   1586          allowOrigin: origin,
   1587          allowMethods: "DELETE",
   1588        },
   1589        {
   1590          server: originSubSub1,
   1591          allowOrigin: origin,
   1592          allowMethods: "DELETE",
   1593        },
   1594      ],
   1595    },
   1596    {
   1597      pass: 0,
   1598      method: "POST",
   1599      body: "hi there",
   1600      headers: { "Content-Type": "text/plain", "my-header": "myValue" },
   1601      hops: [
   1602        { server: "https://example.net" },
   1603        {
   1604          server: originSubSub1,
   1605          allowOrigin: origin,
   1606          allowHeaders: "my-header",
   1607        },
   1608      ],
   1609    },
   1610    {
   1611      pass: 1,
   1612      method: "POST",
   1613      body: "hi there",
   1614      headers: { "Content-Type": "text/plain" },
   1615      hops: [
   1616        { server: origin },
   1617        { server: "https://example.net", allowOrigin: origin },
   1618      ],
   1619    },
   1620    {
   1621      pass: 0,
   1622      method: "POST",
   1623      body: "hi there",
   1624      headers: { "Content-Type": "text/plain", "my-header": "myValue" },
   1625      hops: [
   1626        {
   1627          server: "https://example.net",
   1628          allowOrigin: origin,
   1629          allowHeaders: "my-header",
   1630        },
   1631        {
   1632          server: origin,
   1633          allowOrigin: origin,
   1634          allowHeaders: "my-header",
   1635        },
   1636      ],
   1637    },
   1638  ];
   1639 
   1640  var fetches = [];
   1641  for (test of tests) {
   1642    req = {
   1643      url:
   1644        test.hops[0].server +
   1645        corsServerPath +
   1646        "hop=1&hops=" +
   1647        escape(JSON.stringify(test.hops)),
   1648      method: test.method,
   1649      headers: test.headers,
   1650      body: test.body,
   1651    };
   1652 
   1653    if (test.headers) {
   1654      req.url += "&headers=" + escape(JSON.stringify(test.headers));
   1655    }
   1656 
   1657    if (test.pass) {
   1658      if (test.body) {
   1659        req.url += "&body=" + escape(test.body);
   1660      }
   1661    }
   1662 
   1663    var request = new Request(req.url, {
   1664      method: req.method,
   1665      headers: req.headers,
   1666      body: req.body,
   1667    });
   1668    fetches.push(
   1669      (function (request, test) {
   1670        return fetch(request).then(
   1671          function (res) {
   1672            ok(test.pass, "Expected test to pass for " + JSON.stringify(test));
   1673            is(
   1674              res.status,
   1675              200,
   1676              "wrong status in test for " + JSON.stringify(test)
   1677            );
   1678            is(
   1679              res.statusText,
   1680              "OK",
   1681              "wrong status text for " + JSON.stringify(test)
   1682            );
   1683            is(
   1684              res.type,
   1685              "cors",
   1686              "wrong response type for " + JSON.stringify(test)
   1687            );
   1688            var reqHost = new URL(req.url).host;
   1689            // If there is a service worker present, the redirections will be
   1690            // transparent, assuming that the original request is to the current
   1691            // site and would be intercepted.
   1692            if (isSWPresent) {
   1693              if (reqHost === location.host) {
   1694                is(
   1695                  new URL(res.url).host,
   1696                  reqHost,
   1697                  "Response URL should be original URL with a SW present"
   1698                );
   1699              }
   1700            } else {
   1701              is(
   1702                new URL(res.url).host,
   1703                new URL(test.hops[test.hops.length - 1].server).host,
   1704                "Response URL should be redirected URL"
   1705              );
   1706            }
   1707            return res.text().then(function (v) {
   1708              is(
   1709                v,
   1710                "<res>hello pass</res>\n",
   1711                "wrong responseText in test for " + JSON.stringify(test)
   1712              );
   1713            });
   1714          },
   1715          function (e) {
   1716            ok(!test.pass, "Expected test failure for " + JSON.stringify(test));
   1717            ok(
   1718              e instanceof TypeError,
   1719              "Exception should be TypeError for " + JSON.stringify(test)
   1720            );
   1721          }
   1722        );
   1723      })(request, test)
   1724    );
   1725  }
   1726 
   1727  return Promise.all(fetches);
   1728 }
   1729 
   1730 function testNoCORSRedirects() {
   1731  var origin = self.location.origin;
   1732 
   1733  var tests = [
   1734    { pass: 1, method: "GET", hops: [{ server: "https://example.net" }] },
   1735    {
   1736      pass: 1,
   1737      method: "GET",
   1738      hops: [{ server: origin }, { server: "https://example.net" }],
   1739    },
   1740    {
   1741      pass: 1,
   1742      method: "GET",
   1743      // Must use a simple header due to no-cors header restrictions.
   1744      headers: { "accept-language": "en-us" },
   1745      hops: [{ server: origin }, { server: "https://example.net" }],
   1746    },
   1747    {
   1748      pass: 1,
   1749      method: "GET",
   1750      hops: [
   1751        { server: origin },
   1752        { server: "https://example.net" },
   1753        { server: origin },
   1754      ],
   1755    },
   1756    {
   1757      pass: 1,
   1758      method: "POST",
   1759      body: "upload body here",
   1760      hops: [{ server: origin }, { server: "https://example.net" }],
   1761    },
   1762    {
   1763      pass: 0,
   1764      method: "DELETE",
   1765      hops: [{ server: origin }, { server: "https://example.net" }],
   1766    },
   1767  ];
   1768 
   1769  var fetches = [];
   1770  for (test of tests) {
   1771    req = {
   1772      url:
   1773        test.hops[0].server +
   1774        corsServerPath +
   1775        "hop=1&hops=" +
   1776        escape(JSON.stringify(test.hops)),
   1777      method: test.method,
   1778      headers: test.headers,
   1779      body: test.body,
   1780    };
   1781 
   1782    if (test.headers) {
   1783      req.url += "&headers=" + escape(JSON.stringify(test.headers));
   1784    }
   1785 
   1786    if (test.pass) {
   1787      if (test.body) {
   1788        req.url += "&body=" + escape(test.body);
   1789      }
   1790    }
   1791 
   1792    fetches.push(
   1793      (function (req, test) {
   1794        return new Promise(function (resolve, reject) {
   1795          resolve(
   1796            new Request(req.url, {
   1797              mode: "no-cors",
   1798              method: req.method,
   1799              headers: req.headers,
   1800              body: req.body,
   1801            })
   1802          );
   1803        })
   1804          .then(function (request) {
   1805            return fetch(request);
   1806          })
   1807          .then(
   1808            function (res) {
   1809              ok(
   1810                test.pass,
   1811                "Expected test to pass for " + JSON.stringify(test)
   1812              );
   1813              // All requests are cross-origin no-cors, we should always have
   1814              // an opaque response here.  All values on the opaque response
   1815              // should be hidden.
   1816              is(
   1817                res.type,
   1818                "opaque",
   1819                "wrong response type for " + JSON.stringify(test)
   1820              );
   1821              is(
   1822                res.status,
   1823                0,
   1824                "wrong status in test for " + JSON.stringify(test)
   1825              );
   1826              is(
   1827                res.statusText,
   1828                "",
   1829                "wrong status text for " + JSON.stringify(test)
   1830              );
   1831              is(res.url, "", "wrong response url for " + JSON.stringify(test));
   1832              return res.text().then(function (v) {
   1833                is(
   1834                  v,
   1835                  "",
   1836                  "wrong responseText in test for " + JSON.stringify(test)
   1837                );
   1838              });
   1839            },
   1840            function (e) {
   1841              ok(
   1842                !test.pass,
   1843                "Expected test failure for " + JSON.stringify(test)
   1844              );
   1845              ok(
   1846                e instanceof TypeError,
   1847                "Exception should be TypeError for " + JSON.stringify(test)
   1848              );
   1849            }
   1850          );
   1851      })(req, test)
   1852    );
   1853  }
   1854 
   1855  return Promise.all(fetches);
   1856 }
   1857 
   1858 function testReferrer() {
   1859  var referrer;
   1860  if (self && self.location) {
   1861    referrer = self.location.href;
   1862  } else {
   1863    referrer = document.documentURI;
   1864  }
   1865 
   1866  var dict = {
   1867    Referer: referrer,
   1868  };
   1869  return fetch(
   1870    corsServerPath + "headers=" + encodeURIComponent(JSON.stringify(dict))
   1871  ).then(
   1872    function (res) {
   1873      is(res.status, 200, "expected correct referrer header to be sent");
   1874      dump(res.statusText);
   1875    },
   1876    function (e) {
   1877      ok(false, "expected correct referrer header to be sent");
   1878    }
   1879  );
   1880 }
   1881 
   1882 function runTest() {
   1883  testNoCorsCtor();
   1884  let promise = Promise.resolve();
   1885  if (typeof SpecialPowers === "object") {
   1886    promise = SpecialPowers.pushPrefEnv({
   1887      // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
   1888      set: [["network.cookie.sameSite.laxByDefault", false]],
   1889    });
   1890  }
   1891 
   1892  return promise
   1893    .then(testModeSameOrigin)
   1894    .then(testModeNoCors)
   1895    .then(testModeCors)
   1896    .then(testSameOriginCredentials)
   1897    .then(testCrossOriginCredentials)
   1898    .then(testModeNoCorsCredentials)
   1899    .then(testCORSRedirects)
   1900    .then(testNoCORSRedirects)
   1901    .then(testReferrer);
   1902  // Put more promise based tests here.
   1903 }