// Utility functions to help testing keepalive requests.

// Returns a URL to an iframe that loads a keepalive URL on iframe loaded.
//
// The keepalive URL points to a target that stores `token`. The token will then
// be posted back on iframe loaded to the parent document.
// `method` defaults to GET.
// `frameOrigin` to specify the origin of the iframe to load. If not set,
// default to a different site origin.
// `requestOrigin` to specify the origin of the fetch request target.
// `sendOn` to specify the name of the event when the keepalive request should
// be sent instead of the default 'load'.
// `mode` to specify the fetch request's CORS mode.
// `disallowCrossOrigin` to ask the iframe to set up a server that disallows
// cross origin requests.
function getKeepAliveIframeUrl(token, method, {
  frameOrigin = 'DEFAULT',
  requestOrigin = '',
  sendOn = 'load',
  mode = 'cors',
  disallowCrossOrigin = false
} = {}) {
  const https = location.protocol.startsWith('https');
  frameOrigin = frameOrigin === 'DEFAULT' ?
      get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN'] :
      frameOrigin;
  return `${frameOrigin}/fetch/api/resources/keepalive-iframe.html?` +
      `token=${token}&` +
      `method=${method}&` +
      `sendOn=${sendOn}&` +
      `mode=${mode}&` + (disallowCrossOrigin ? `disallowCrossOrigin=1&` : ``) +
      `origin=${requestOrigin}`;
}

// Returns a different-site URL to an iframe that loads a keepalive URL.
//
// By default, the keepalive URL points to a target that redirects to another
// same-origin destination storing `token`. The token will then be posted back
// to parent document.
//
// The URL redirects can be customized from `origin1` to `origin2` if provided.
// Sets `withPreflight` to true to get URL enabling preflight.
function getKeepAliveAndRedirectIframeUrl(
    token, origin1, origin2, withPreflight) {
  const https = location.protocol.startsWith('https');
  const frameOrigin =
      get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN'];
  return `${frameOrigin}/fetch/api/resources/keepalive-redirect-iframe.html?` +
      `token=${token}&` +
      `origin1=${origin1}&` +
      `origin2=${origin2}&` + (withPreflight ? `with-headers` : ``);
}

async function iframeLoaded(iframe) {
  return new Promise((resolve) => iframe.addEventListener('load', resolve));
}

// Obtains the token from the message posted by iframe after loading
// `getKeepAliveAndRedirectIframeUrl()`.
async function getTokenFromMessage() {
  return new Promise((resolve) => {
    window.addEventListener('message', (event) => {
      resolve(event.data);
    }, {once: true});
  });
}

// Tells if `token` has been stored in the server.
async function queryToken(token) {
  const response = await fetch(`../resources/stash-take.py?key=${token}`);
  const json = await response.json();
  return json;
}

// A helper to assert the existence of `token` that should have been stored in
// the server by fetching ../resources/stash-put.py.
//
// This function simply wait for a custom amount of time before trying to
// retrieve `token` from the server.
// `expectTokenExist` tells if `token` should be present or not.
//
// NOTE:
// In order to parallelize the work, we are going to have an async_test
// for the rest of the work. Note that we want the serialized behavior
// for the steps so far, so we don't want to make the entire test case
// an async_test.
function assertStashedTokenAsync(
    testName, token, {expectTokenExist = true} = {}) {
  async_test(test => {
    new Promise(resolve => test.step_timeout(resolve, 3000 /*ms*/))
        .then(test.step_func(() => {
          return queryToken(token);
        }))
        .then(test.step_func(result => {
          if (expectTokenExist) {
            assert_equals(result, 'on', `token should be on (stashed).`);
            test.done();
          } else {
            assert_not_equals(
                result, 'on', `token should not be on (stashed).`);
            return Promise.reject(`Failed to retrieve token from server`);
          }
        }))
        .catch(test.step_func(e => {
          if (expectTokenExist) {
            test.unreached_func(e);
          } else {
            test.done();
          }
        }));
  }, testName);
}

/**
 * In an iframe, and in `load` event handler, test to fetch a keepalive URL that
 * involves in redirect to another URL.
 *
 * `unloadIframe` to unload the iframe before verifying stashed token to
 * simulate the situation that unloads after fetching. Note that this test is
 * different from `keepaliveRedirectInUnloadTest()` in that the the latter
 * performs fetch() call directly in `unload` event handler, while this test
 * does it in `load`.
 */
function keepaliveRedirectTest(desc, {
  origin1 = '',
  origin2 = '',
  withPreflight = false,
  unloadIframe = false,
  expectFetchSucceed = true,
} = {}) {
  desc = `[keepalive][iframe][load] ${desc}` +
      (unloadIframe ? ' [unload at end]' : '');
  promise_test(async (test) => {
    const tokenToStash = token();
    const iframe = document.createElement('iframe');
    iframe.src = getKeepAliveAndRedirectIframeUrl(
        tokenToStash, origin1, origin2, withPreflight);
    document.body.appendChild(iframe);
    await iframeLoaded(iframe);
    assert_equals(await getTokenFromMessage(), tokenToStash);
    if (unloadIframe) {
      iframe.remove();
    }

    assertStashedTokenAsync(
        desc, tokenToStash, {expectTokenExist: expectFetchSucceed});
  }, `${desc}; setting up`);
}

/**
 * Opens a different site window, and in `unload` event handler, test to fetch
 * a keepalive URL that involves in redirect to another URL.
 */
function keepaliveRedirectInUnloadTest(desc, {
  origin1 = '',
  origin2 = '',
  url2 = '',
  withPreflight = false,
  expectFetchSucceed = true
} = {}) {
  desc = `[keepalive][new window][unload] ${desc}`;

  promise_test(async (test) => {
    const targetUrl =
        `${HTTP_NOTSAMESITE_ORIGIN}/fetch/api/resources/keepalive-redirect-window.html?` +
        `origin1=${origin1}&` +
        `origin2=${origin2}&` +
        `url2=${url2}&` + (withPreflight ? `with-headers` : ``);
    const w = window.open(targetUrl);
    const token = await getTokenFromMessage();
    w.close();

    assertStashedTokenAsync(
        desc, token, {expectTokenExist: expectFetchSucceed});
  }, `${desc}; setting up`);
}

/**
* utility to create pending keepalive fetch requests
* The pending request state is achieved by ensuring the server (trickle.py) does not
* immediately respond to the fetch requests.
* The response delay is set as a url parameter.
*/

function createPendingKeepAliveRequest(delay, remote = false) {
  // trickle.py is a script that can make a delayed response to the client request
  const trickleRemoteURL = get_host_info().HTTPS_REMOTE_ORIGIN + '/fetch/api/resources/trickle.py?count=1&ms=';
  const trickleLocalURL = get_host_info().HTTP_ORIGIN + '/fetch/api/resources/trickle.py?count=1&ms=';
  url = remote ? trickleRemoteURL : trickleLocalURL;

  const body = '*'.repeat(10);
  return fetch(url + delay, { keepalive: true, body, method: 'POST' }).then(res => {
      return res.text();
  }).then(() => {
      return new Promise(resolve => step_timeout(resolve, 1));
  }).catch((error) => {
      return Promise.reject(error);;
  })
}
