"use strict";

const { Downloads } = ChromeUtils.importESModule(
  "resource://gre/modules/Downloads.sys.mjs"
);

const server = createHttpServer();
server.registerDirectory("/data/", do_get_file("data"));

const BASE = `http://localhost:${server.identity.primaryPort}/data`;
const TEST_FILE = "file_download.txt";
const TEST_URL = BASE + "/" + TEST_FILE;

// We use different cookieBehaviors so that we can verify if we use the correct
// cookieBehavior if option.incognito is set. Note that we need to set a
// non-default value to the private cookieBehavior because the private
// cookieBehavior will mirror the regular cookieBehavior if the private pref is
// default value and the regular pref is non-default value. To avoid affecting
// the test by mirroring, we set the private cookieBehavior to a non-default
// value.
const TEST_REGULAR_COOKIE_BEHAVIOR =
  Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
const TEST_PRIVATE_COOKIE_BEHAVIOR = Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN;

let downloadDir;

function observeDownloadChannel(uri, partitionKey, isPrivate) {
  return new Promise(resolve => {
    let observer = {
      observe(subject, topic) {
        if (topic === "http-on-modify-request") {
          let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
          if (httpChannel.URI.spec != uri) {
            return;
          }

          let reqLoadInfo = httpChannel.loadInfo;
          let cookieJarSettings = reqLoadInfo.cookieJarSettings;

          // Check the partitionKey of the cookieJarSettings.
          equal(
            cookieJarSettings.partitionKey,
            partitionKey,
            "The loadInfo has the correct paritionKey"
          );

          // Check the cookieBehavior of the cookieJarSettings.
          equal(
            cookieJarSettings.cookieBehavior,
            isPrivate
              ? TEST_PRIVATE_COOKIE_BEHAVIOR
              : TEST_REGULAR_COOKIE_BEHAVIOR,
            "The loadInfo has the correct cookieBehavior"
          );

          Services.obs.removeObserver(observer, "http-on-modify-request");
          resolve();
        }
      },
    };

    Services.obs.addObserver(observer, "http-on-modify-request");
  });
}

async function waitForDownloads() {
  let list = await Downloads.getList(Downloads.ALL);
  let downloads = await list.getAll();

  let inprogress = downloads.filter(dl => !dl.stopped);
  return Promise.all(inprogress.map(dl => dl.whenSucceeded()));
}

function backgroundScript() {
  browser.test.onMessage.addListener(async (msg, ...args) => {
    if (msg == "download.request") {
      let options = args[0];

      try {
        let id = await browser.downloads.download(options);
        browser.test.sendMessage("download.done", { status: "success", id });
      } catch (error) {
        browser.test.sendMessage("download.done", {
          status: "error",
          errmsg: error.message,
        });
      }
    }
  });

  browser.test.sendMessage("ready");
}

// Remove a file in the downloads directory.
function remove(filename, recursive = false) {
  let file = downloadDir.clone();
  file.append(filename);
  file.remove(recursive);
}

add_task(function setup() {
  downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
  downloadDir.createUnique(
    Ci.nsIFile.DIRECTORY_TYPE,
    FileUtils.PERMS_DIRECTORY
  );
  info(`Using download directory ${downloadDir.path}`);

  Services.prefs.setIntPref("browser.download.folderList", 2);
  Services.prefs.setComplexValue(
    "browser.download.dir",
    Ci.nsIFile,
    downloadDir
  );

  Services.prefs.setIntPref(
    "network.cookie.cookieBehavior",
    TEST_REGULAR_COOKIE_BEHAVIOR
  );
  Services.prefs.setIntPref(
    "network.cookie.cookieBehavior.pbmode",
    TEST_PRIVATE_COOKIE_BEHAVIOR
  );

  registerCleanupFunction(() => {
    Services.prefs.clearUserPref("browser.download.folderList");
    Services.prefs.clearUserPref("browser.download.dir");
    Services.prefs.clearUserPref("network.cookie.cookieBehavior");
    Services.prefs.clearUserPref("network.cookie.cookieBehavior.pbmode");

    let entries = downloadDir.directoryEntries;
    while (entries.hasMoreElements()) {
      let entry = entries.nextFile;
      info(`Leftover file ${entry.path} in download directory`);
      ok(false, `Leftover file ${entry.path} in download directory`);
      entry.remove(false);
    }

    downloadDir.remove(false);
  });
});

add_task(async function test() {
  let extension = ExtensionTestUtils.loadExtension({
    background: `(${backgroundScript})()`,
    manifest: {
      permissions: ["downloads"],
    },
    incognitoOverride: "spanning",
  });

  function download(options) {
    extension.sendMessage("download.request", options);
    return extension.awaitMessage("download.done");
  }

  async function testDownload(url, partitionKey, isPrivate) {
    let options = { url, incognito: isPrivate };

    let promiseObserveDownloadChannel = observeDownloadChannel(
      url,
      partitionKey,
      isPrivate
    );

    let msg = await download(options);
    equal(msg.status, "success", `downloads.download() works`);

    await promiseObserveDownloadChannel;
    await waitForDownloads();
  }

  await extension.startup();
  await extension.awaitMessage("ready");
  info("extension started");

  // Call download() to check partitionKey of the download channel for the
  // regular browsing mode.
  await testDownload(TEST_URL, `(http,localhost)`, false);
  remove(TEST_FILE);

  // Call download again for the private browsing mode.
  await testDownload(TEST_URL, `(http,localhost)`, true);
  remove(TEST_FILE);

  await extension.unload();
});
